ZJOI2005 午餐
嗯……我承认我看了题解,不过好歹有了点自己的思路,大约蒙出来了\(30\%\)(个人感觉)……
学会\(DP\),任重而道远啊!
Step1.贪心排序
先将每个人按吃饭的快慢排序,然后再进行\(DP\)
稍微证明一下这个贪心吧
证明
设两个人排队和吃饭的时间分别为\(a_1,b_1\)和\(a_2,b_2\), 且\(b_1 > b_2\),那么\(1\)同学在前时,所花费的总时间为\((a_1+b_1)+(a_2+b_2-b_1)\)(因为\(1\)同学吃饭的时间和\(2\)同学打饭并吃饭的时间是重叠的,所以要减去)
化简后为\(a_1+a_2+b_2\)
同理,如果\(2\)同学在前,总时间为\(a_1+a_2+b_1\)
因为\(b_1 > b_2\),所以\(1\)同学在前时花费的总时间少
当然,如果\(b_1 \geq a_2+b_2\)时,\((a_1+b_1)+(a_2+b_2-b_1)\)化简后应为\(a_1+b_1\)(因为不可能存在负数时间)
但即使这样,\(1\)同学在前仍然更优,因为\(a_1+b_1\)显然\(<a_1+a_2+b_1\)
Step2.DP
状态
设f[i][j]
表示前\(i\)个人在\(1\)号窗口排队打饭的时间为\(j\)时,吃完饭的时间
转移方程
- 当第\(i\)个人在\(1\)号窗口打饭时
if(p[i].a<=j) f[i][j]=min(f[i][j],max(f[i-1][j-p[i].a],j+p[i].b));
因为全部吃完饭的时刻为最后一个人吃完饭的时刻,即\(max \{ i\)同学打完饭的时间\(+i\)同学吃饭的时间\()\),而新加进来的这名同学有可能是最后一个吃完的,所以要max(f[i-1][j-p[i].a],j+p[i].b))
,即在加这位同学之前的最晚时间和这位同学吃完饭的时间中取一个最大值。
- 当第\(i\)个人在\(2\)号窗口打饭时
f[i][j]=min(f[i][j],max(f[i-1][j],suma[i]-j+p[i].b));
\(max\)函数同理,suma[i]
表示前\(i\)位同学打饭所花的时间
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int read(){
int k=0; char c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9')
k=k*10+c-48, c=getchar();
return k;
}
struct zzz{
int a,b;
}p[210];
bool cmp(zzz x,zzz y){
return x.b > y.b;
}
int f[210][40010],f2[210],ti[210][210],suma[210];
int main(){
int n=read();
for(int i=1;i<=n;i++)
p[i].a=read(),p[i].b=read();
sort(p+1,p+n+1,cmp);
for(int i=1;i<=n;i++)
suma[i]=suma[i-1]+p[i].a;
memset(f,127,sizeof(f));
f[0][0]=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=suma[i];j++){
if(p[i].a<=j) 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],suma[i]-j+p[i].b));
}
}
int ans=2147483647;
for(int i=1;i<=suma[n];i++)
ans=min(ans,f[n][i]);
cout<<ans;
return 0;
}