题目描述
上午的训练结束了,THU ACM小组集体去吃午餐,他们一行 (N) 人来到了著名的十食堂。这里有两个打饭的窗口,每个窗口同一时刻只能给一个人打饭。由于每个人的口味(以及胃口)不同,所以他们要吃的菜各有不同,打饭所要花费的时间是因人而异的。另外每个人吃饭的速度也不尽相同,所以吃饭花费的时间也是可能有所不同的。
THU ACM小组的吃饭计划是这样的:先把所有的人分成两队,并安排好每队中各人的排列顺序,然后一号队伍到一号窗口去排队打饭,二号队伍到二号窗口去排队打饭。每个人打完饭后立刻开始吃,所有人都吃完饭后立刻集合去六教地下室进行下午的训练。
现在给定了每个人的打饭时间和吃饭时间,要求安排一种最佳的分队和排队方案使得所有人都吃完饭的时间尽量早。
假设THU ACM小组在时刻 (0) 到达十食堂,而且食堂里面没有其他吃饭的同学(只有打饭的师傅)。每个人必须而且只能被分在一个队伍里。两个窗口是并行操作互不影响的,而且每个人打饭的时间是和窗口无关的,打完饭之后立刻就开始吃饭,中间没有延迟。
现在给定N个人各自的打饭时间和吃饭时间,要求输出最佳方案下所有人吃完饭的时刻。
输入格式
第一行一个整数 (N) ,代表总共有 (N) 个人。
以下N行,每行两个整数 (Ai) ,(Bi) 。依次代表第 (i) 个人的打饭时间和吃饭时间。
输出格式
一个整数 (T) ,代表所有人吃完饭的最早时刻。
输入输出样例
输入 #1
5
2 2
7 7
1 3
6 4
8 5
输出 #1
17
说明/提示
所有输入数据均为不超过 (200) 的正整数。
————————————————————————————————————
这道题思维难度有点大,我是看了眼其他大佬的题解才会写的,在这里重新总结一下。
这道题看起来和经典的接水问题有相似之处,但是比接水问题多了“吃饭”这一过程以及另外的一个队列,要求我们制定出更加精妙的DP方程。
但是我们依然可以首先借鉴接水问题的贪心思路,即让吃饭速度慢的同学先打饭,最优性可以使用反证法证明。
于是我们获得了一个相对的打饭顺序(同时考虑两个队列),所以我们这样设立方程:
(f[i][j][k]) 表示前 (i) 个人在一号窗口打饭用时为 (i) ,在二号窗口打饭用时为 (j) 并且所有人都吃完饭的最短用时。
注意到这个方程的空间复杂度是 (200^5) 的,会MLE。
我们可以在排序之后用 (sum) 数组存一下前 (i) 个人打饭用时的前缀和,因为每人必然会在其中一个窗口打饭,所以可以用 %sum[i] - j% 算出在人们在二号窗口打饭的用时。
于是DP方程的第三维可以被优化掉,空间复杂度变成了 (200^3),可以接受。
如何进行状态转移?
首先考虑把当前的这个人放入一号队列的情况,
转移方程为:$ f[i][j]=min(f[i][j],max(f[i-1][j-p[i].a],j+p[i].b)) $
其中 (max()) 里面的两个元素分别表示当前的这个人打饭和吃饭速度都贼快,前面打饭的人还没吃完就已经打完饭并且吃完了和当前的这个人吃完饭的时间是一号队列中最迟的(对此前的答案有贡献)两种情况。
由于每个变量值都是确定的,所以这两种情况要同时考虑。最后在这两值里面取最大值,再让 (f[i][j]) 取用时最短的大情况。
把当前人放入二号队列的情况同理,只不过是把 (j) 用 (sum[i] - j) 替换。
(f[i][j]=min(f[i][j],max(f[i-1][j],sum[i]-j+p[i].b)))
这样 (f) 数组就存了两个队列中所有人都吃完饭用时较长的一个。
代码如下:
#include <bits/stdc++.h>
#define FOR(i,s,t) for(int (i)=(s);(i)<=(t);(i)++)
#define MAXN 207
#define INF 0x3f3f3f3f
using namespace std;
struct Person { int a,b; }p[MAXN];
int n,ans=INF,sum[MAXN],f[MAXN][MAXN*MAXN];
inline bool cmp(const Person &A,const Person &B) { return A.b>B.b; }
inline int read() {
int w=0,X=0; char ch=0;
while (!isdigit(ch)) w|=ch=='-',ch=getchar();
while (isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
return w?-X:X;
}
int main() {
memset(f,INF,sizeof(f));
memset(sum,0,sizeof(sum));
n=read();
FOR(i,1,n) p[i].a=read(),p[i].b=read();
sort(p+1,p+n+1,cmp);
FOR(i,1,n) sum[i]=sum[i-1]+p[i].a;
f[1][0]=f[1][p[1].a]=p[1].a+p[1].b;
for (int i=1;i<=n;i++) {
for (int j=1;j<=sum[i];j++) {
if (j>=p[i].a) f[i][j]=min(f[i][j],max(f[i-1][j-p[i].a],j+p[i].b));
f[i][j]=min(f[i][j],max(f[i-1][j],sum[i]-j+p[i].b));
if (i==n) ans=min(ans,f[i][j]);
}
}
printf("%d",ans);
return 0;
}