[二分][dp] Jzoj P3463 军训

Description

HYSBZ 开学了!今年HYSBZ 有n 个男生来上学,学号为1…n,每个学生都必须参加军训。在这种比较堕落的学校里,每个男生都会有Gi 个女朋友,而且每个人都会有一个欠扁值Hi。学校为了保证军训时教官不会因为学生们都是人生赢家或者是太欠扁而发生打架事故,所以要把学生们分班,并做出了如下要求:

1.分班必须按照学号顺序来,即不能在一个班上出现学号不连续的情况。

2.每个学生必须要被分到某个班上。

3.每个班的欠扁值定义为该班中欠扁值最高的那名同学的欠扁值。所有班的欠扁值之和不得超过Limit。

4.每个班的女友指数定义为该班中所有同学的女友数量之和。在满足条件1、2、3 的情况下,分班应使得女友指数最高的那个班的女友指数最小。

请你帮HYSBZ 的教务处完成分班工作,并输出女友指数最高的班级的女友指数。

输入数据保证题目有解。
 

Input

第一行仅2 个正整数n, Limit,分别为学生数量和欠扁值之和的上限。

接下来n 行每行2 个正整数Hi,Gi,分别为学号为i 的学生的欠扁值和女友数。

Output

仅1 个正整数,表示满足分班的条件下女友指数最高的班级的女友指数。
 

Sample Input

4 6
4 3
3 5
2 2
2 4

Sample Output

8
【样例解释】
分班按照(1,2),(3,4)进行,这时班级欠扁值之和为4+2=6<=Limit,而女友指数最高的班级为(1,2),为8。容易看出该分班方案可得到最佳答案。
 
 

Data Constraint

对于20%的数据:n,Limit<=100

对于40%的数据:n<=1000

对于100%的数据:1<=n,Gi<=20000,1<=Hi,Limit<=10^7

题解

  • 一看题目,要我们求最大值最小,显然二分
  • 设二分出一个t表示分班后的最大的女友指数,显然后面划分出来的所有班都要小于t
  • 那么现在问题转换为求这样分班后是否可行
  • 题目还有一个条件:所有班的欠扁值之和不得超过Limit
  • 考虑一下dp
  • 设f[i]为分班到第i位的最小欠扁值和
  • f[i]=min(f[j]+max(h[j]...h[i]),f[i])且
  • 这公式显然可以推出来
  • 这样的话时间复杂度就是O(n log n^2),显然会炸
  • 这样的话,考虑再二分出分组的界线,用前缀和sum[i]表示前i个人的g[i]的和
  • 对于欠扁值的话,也就是在区间里取最大值

代码

 1 #include <cstdio>
 2 #include <iostream>
 3 #include <cstring>
 4 #include <algorithm>
 5 using namespace std;
 6 const int inf=10000000000;
 7 int n,limit,h[20010],g[20010],sum[20010],last[20010],f[20010],z[20010],x[20010],tot,l,r,mid;
 8 bool check(int x)
 9 {
10     int l,r,mx,ans,mid;
11     memset(f,127,sizeof(f));
12     f[1]=0;
13     for (int i=1;i<=n;i++)
14     {
15         l=i; r=n;
16         while (l<r)
17         {
18             mid=(l+r)/2;
19             if (sum[mid]-sum[i-1]<=x) l=mid+1; else r=mid;
20         }
21         if (sum[r]-sum[i-1]<=x) r++;
22         ans=0; l=i; mx=0;
23         while (l<r)
24         {
25             ans+=g[l];
26             f[l]=min(f[l],f[i]+mx);
27             mx=h[l]; l=last[l];
28         }
29         f[r]=min(f[r],f[i]+mx);
30     }
31     if (f[n+1]<=limit) return true; else return false;
32 }
33 int main()
34 {
35     scanf("%d%d",&n,&limit);
36     for (int i=1;i<=n;i++)
37     {
38         scanf("%d%d",&h[i],&g[i]);
39         sum[i]=sum[i-1]+g[i];
40     }
41     z[1]=inf; x[1]=n+1; tot=1;
42     for (int i=n;i>=1;i--)
43     {
44         while (h[i]>=z[tot]) tot--;
45         last[i]=x[tot];
46         tot++;
47         z[tot]=h[i]; x[tot]=i;
48     }
49     l=1,r=sum[n];
50     while (l<r)
51     {
52         mid=(l+r)/2;
53         if (check(mid)) r=mid; else l=mid+1;
54     }
55     printf("%d",l);
56 }
原文地址:https://www.cnblogs.com/Comfortable/p/9337992.html