题解:luoguP2577【ZJOI2005】午餐(DP)
题目描述
上午的训练结束了,THU ACM小组集体去吃午餐,他们一行N人来到了著名的十食堂。这里有两个打饭的窗口,每个窗口同一时刻只能给一个人打饭。由于每个人的口味(以及胃口)不同,所以他们要吃的菜各有不同,打饭所要花费的时间是因人而异的。另外每个人吃饭的速度也不尽相同,所以吃饭花费的时间也是可能有所不同的。
THU ACM小组的吃饭计划是这样的:先把所有的人分成两队,并安排好每队中各人的排列顺序,然后一号队伍到一号窗口去排队打饭,二号队伍到二号窗口去排队打饭。每个人打完饭后立刻开始吃,所有人都吃完饭后立刻集合去六教地下室进行下午的训练。
现在给定了每个人的打饭时间和吃饭时间,要求安排一种最佳的分队和排队方案使得所有人都吃完饭的时间尽量早。
假设THU ACM小组在时刻0到达十食堂,而且食堂里面没有其他吃饭的同学(只有打饭的师傅)。每个人必须而且只能被分在一个队伍里。两个窗口是并行操作互不影响的,而且每个人打饭的时间是和窗口无关的,打完饭之后立刻就开始吃饭,中间没有延迟。
现在给定N个人各自的打饭时间和吃饭时间,要求输出最佳方案下所有人吃完饭的时刻。
输入输出格式
输入格式:
第一行一个整数N,代表总共有N个人。
以下N行,每行两个整数 Ai,Bi。依次代表第i个人的打饭时间和吃饭时间。
输出格式:
一个整数T,代表所有人吃完饭的最早时刻。
输入输出样例
说明
所有输入数据均为不超过200的正整数。
题解:
首先,对于一个窗口,队伍中各个人的排列顺序对该窗口的打饭总时间是无影响的。所以有这样的贪心思路:在该窗口,让吃饭时间越长的越先打饭,这样可以使该窗口所有人打完饭并吃完的时刻最早
因此不难想到这样的DP思路:用f[i][j][k]表示让前i个人恰好吃完饭,在1号窗口花费j时间,在2号窗口花费k时间时的最早集合时刻。
对人按吃饭时间排序后,可以得到这样的状态转移方程:
f[i][j][k]=min(f[i][j][k],max(f[i-1][j-a[i]][k],j+b[i]),max(f[i][j][k-a[i]],k+b[i]));
但有个问题,这样的话,需要开一个200*40000*40000的数组,显然是会爆空间的
因此考虑降维
我们想:既然排队顺序不影响某个窗口的打饭时间,那么可以用t[i]表示前i个人打饭的总时间,这时候k=t[i]-j;由此就可以减去一维了。用f[i][j]表示前i个人恰好吃完饭,在1号窗口花费j时间打饭的最早集合时刻,k可以由计算得。
状态转移方程如下:
f[i][j]=min(f[i][j],max(f[i-1][j-a[i]],j+b[i]),max(t[i]-j+b[i],f[i-1][j]));
代码:
#include<bits/stdc++.h> #define INF 1e9 using namespace std; struct people{ int a,b; }p[205]; int f[205][40000],t[205]; int n,ans=INF; int minx(int a1,int b1) { return a1<b1?a1:b1; } int maxn(int a2,int b2) { return a2>b2?a2:b2; } bool cmp(people s,people k) { return s.b>k.b; } void dp() { f[1][0]=f[1][p[1].a]=p[1].b+p[1].a; for(int i=2;i<=n;i++) for(int j=0;j<=t[i];j++) { if(j-p[i].a>=0) f[i][j]=minx(f[i][j],maxn(f[i-1][j-p[i].a],j+p[i].b)); f[i][j]=minx(f[i][j],maxn(t[i]-j+p[i].b,f[i-1][j])); } } int main() { scanf("%d",&n); t[0]=0; for(int i=1;i<=n;i++) scanf("%d%d",&p[i].a,&p[i].b); sort(p+1,p+1+n,cmp); for(int i=1;i<=n;i++) t[i]=t[i-1]+p[i].a; for(int i=1;i<=n;i++) for(int j=0;j<=t[n];j++) f[i][j]=INF; dp(); for(int i=0;i<=t[n];i++) ans=minx(ans,f[n][i]); printf("%d",ans); return 0; }