[ZJOI2005]午餐
题目描述
上午的训练结束了,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的正整数。
Solution
首先一个显而易见的贪心:一个人吃饭的时间越久,为了达成我们的目标,我们要尽量让他先吃
所以按吃饭时间从大到小排序
首先,我们知道肯定是需要枚举当前是第几个人打饭的,但是仅仅这样不足以确定我们的状态,所以我们还要确定当前情况下1号窗口和2号窗口分别已经排了多长时间的队。因此,dp[i][j][k]表示当前处理到第i个人1号窗口排队时间为j,2号窗口排队时间为k的最小吃饭时间
if(j>=t[i].a) dp[i][j][k]=min(dp[i][j][k],max(dp[i-1][j-t[i].a][k],j+t[i].b));
//dp[i-1][j-t[i].a][k]表示第i个人排队和吃饭的时间还不足前i-1个人吃饭的时间,所以没有造成影响
//后面的j+t[i].b就是造成了影响
if(k>=t[i].a) dp[i][j][k]=min(dp[i][j][k],max(dp[i-1][j][k-t[i].a],k+t[i].b));
//同上
我们发现这个数组会爆内存,怎么优化呢?
无论是选1号窗口还是2号窗口,前i个人的排队时间总和是不变的,我们就可以通过j求出k,前缀和就好了
那么方程就变成了这样
if(j>=t[i].a) dp[i][j]=min(dp[i][j],max(dp[i-1][j-t[i].a],j+t[i].b));
dp[i][j]=min(dp[i][j],max(dp[i-1][j],sum[i]-j+t[i].b));//sum[i]-j就是k
dp边界:dp[0][0]=0,其他的都赋为inf就好了,毕竟是要求最小值嘛
Code
#include<bits/stdc++.h>
using namespace std;
const int N=210;
struct node {
int a,b;
bool operator < (const node &c) const{return b>c.b;}
}t[N];
int n,ans;
int sum[N],dp[N][N*N];
int main()
{
ios::sync_with_stdio(0);
cin>>n;
for(int i=1;i<=n;i++)
cin>>t[i].a>>t[i].b;
sort(t+1,t+1+n);
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+t[i].a;
memset(dp,0x3f,sizeof(dp)); ans=dp[0][0],dp[0][0]=0;
for(int i=1;i<=n;i++) {
for(int j=0;j<=sum[i];j++) {
if(j>=t[i].a) dp[i][j]=min(dp[i][j],max(dp[i-1][j-t[i].a],j+t[i].b));
dp[i][j]=min(dp[i][j],max(dp[i-1][j],sum[i]-j+t[i].b));
}
}
for(int i=1;i<=sum[n];i++)
ans=min(ans,dp[n][i]);
cout<<ans<<endl;
}