题解 P2577 【[ZJOI2005]午餐】

wjyyy

2018-09-20 14:47:56

Solution

安利博客->[wjyyy](http://www.wjyyy.top/1711.html) ## 题解: 首先这个题需要用贪心来将每个人排序,否则无法进行唯一的DP。但是贪心会有两个思路,分别是按**吃饭时间降序排序,和按打饭+吃饭时间之和降序排序**,因为我们要避免吃饭时间长的人后打饭。因此下面的证明只考虑这两种情况。 设第$i$个人的打饭时间为$a_i$,吃饭时间为$b_i$,令$a_i+b_i>a_{i+1}+b_{i+1}$(如果小于的话当然要把既满足总时间长又满足吃饭时间长的人放在前面),**但$b_i<b_{i+1}$**,则$a_i>a_{i+1}$。即第$i+1$个人的吃饭时间较长。假设只有一列队,如果将$i$排在前面,那么总时间为$f_1=\max(a_i+b_i,a_i+a_{i+1}+b_{i+1})=a_i+a_{i+1}+b_{i+1}$,因为$a_i+b_{i+1}>a_i+b_i$。而将$i$排在后面,则总时间为$f_2=\max(a_{i+1}+a_i+b_i,a_{i+1}+b_{i+1})=a_{i+1}+a_i+b_i$ 。那我们比较$f_1$和$f_2$,$f_1-f_2=b_{i+1}-b_i>0$,因此$f_1>f_2$。**所以贪心的结论是把吃饭时间长的人排在前面。** 这样可以先把每个人排序,然后就可以做一个类似背包的DP了。设f[i][j][k]表示排完了$i$个人,第一队**结束排队**时间为$j$,第二队结束时间为$k$的最早**吃完饭的时间**。这样直接做转移就可以了,$f[i][j][k]=\min(\max(f[i-1][j-a[i]][k],j+b[i]),\max(f[i-1][j][k-a[i]],k+b[i]))$。但是这样的时间复杂度是$O(n^5)$,因为状态共有$200\times 200^2\times 200^2$个。 考虑到时间复杂度主要由状态个数贡献,因此考虑压维。我们看到,当贪心地排序后,序列是一定的。因此$j+k$也就成了定值,也就是说,我们在DP数组中存下$j$,利用预处理的前缀和$sum$减去这个$j$,就是所求的$k$了。这样状态就只剩下$n^3$,是可以过的,相应地改一下转移方程就可以了(把$k$代换为$sum[i]-j$即可)。 这个题的主要思维在贪心。同时较难的地方还有压掉一维,不过见得多了也许就是一种套路了吧。 ## Code: ```cpp #include<cstdio> #include<cstring> #include<algorithm> using std::min; using std::max; struct pp { int x,y; friend bool operator <(pp a,pp b) { return a.y>b.y; } }a[222]; int f[205][40005]; int main() { memset(f,0x3f,sizeof(f)); f[0][0]=0; int n; scanf("%d",&n); for(int i=1;i<=n;++i) scanf("%d%d",&a[i].x,&a[i].y); std::sort(a+1,a+1+n); int tt=0; for(int i=1;i<=n;++i) { for(int j=0;j<=200*i;++j) if(j<a[i].x) f[i][j]=max(f[i-1][j],tt-j+a[i].x+a[i].y); else f[i][j]=min(max(f[i-1][j-a[i].x],j+a[i].y),max(f[i-1][j],tt-j+a[i].x+a[i].y)); tt+=a[i].x; } long long ans=0x7ffffffffff; for(int i=0;i<=200*n;++i) ans=ans<f[n][i]?ans:f[n][i]; printf("%lld\n",ans); return 0; } ```