P2577 [ZJOI2005]午餐[DP]

题目描述

上午的训练结束了,THU ACM小组集体去吃午餐,他们一行N人来到了著名的十食堂。这里有两个打饭的窗口,每个窗口同一时刻只能给一个人打饭。由于每个人的口味(以及胃口)不同,所以他们要吃的菜各有不同,打饭所要花费的时间是因人而异的。另外每个人吃饭的速度也不尽相同,所以吃饭花费的时间也是可能有所不同的。

THU ACM小组的吃饭计划是这样的:先把所有的人分成两队,并安排好每队中各人的排列顺序,然后一号队伍到一号窗口去排队打饭,二号队伍到二号窗口去排队打饭。每个人打完饭后立刻开始吃,所有人都吃完饭后立刻集合去六教地下室进行下午的训练。

现在给定了每个人的打饭时间和吃饭时间,要求安排一种最佳的分队和排队方案使得所有人都吃完饭的时间尽量早。

假设THU ACM小组在时刻0到达十食堂,而且食堂里面没有其他吃饭的同学(只有打饭的师傅)。每个人必须而且只能被分在一个队伍里。两个窗口是并行操作互不影响的,而且每个人打饭的时间是和窗口无关的,打完饭之后立刻就开始吃饭,中间没有延迟。

现在给定N个人各自的打饭时间和吃饭时间,要求输出最佳方案下所有人吃完饭的时刻。

解析

真的难,看了题解发现自己思路没问题,状态却迟迟想不出来,状态转移也只是yy出来个大概。


首先想到要对序列排序,容易想到吃饭慢而打饭快的人对后面的人造成的影响是最小的,因此我们把这种人排在前面。此时就可以以打饭打到某个人为阶段做dp。

显然我们在转移时要同时考虑两个队列的用时情况,但是把什么写在状态里,把什么存起来又是个大问题,反正我是在这里懵圈了。最开始我思考了一个\(dp[0/1][0/1][i]\),想着把每一队最早打饭时间和最早吃完时间都存起来,于是发现没法转移。

由于题目要求吃完饭的时间,我们理所当然地要把从\(1\sim i\)个人的吃完饭的总时间存进dp数组,然而用什么作状态又是个大问题。然后我就寻思着\(dp[i][h][k]\)存最早吃完饭的时间,\(h,k\)分别表示每一队打完饭的时间行不?然而这样会爆空间。

无奈,看了看题解。

以上是瞎bb,以下是正解。


\(dp[i][j]\)表示前\(i\)个人在某一队打了\(j\)时间的饭时,最早吃完的时间。此时我们维护一个打饭时间的前缀和,那么另一队的打饭时间也就出来了,那么转移也就很好想了。

情况一:

转移时,我们要考虑把新的一个人加入两个队列的其中一个,使得整体吃饭时间最少。那么显然,如果这个人加入某一队,其打完饭并吃完饭之后他前面的人还没吃完这种情况是最优的,因为没有对整体吃饭时间造成任何影响

情况二:

其次若这个人加入任意一队都不会出现上述情况,即无论如何他加进哪一队,整体吃饭时间都会变多,那我们退而求其次,找出这个人造成的影响最小的一队,然后把他加进去。影响最小即,使整体吃饭时间增加得最少

那么就很容易写出转移方程了。

\(a[i].a\)表示\(i\)的打饭时间,\(a[i].b\)表示\(i\)的吃饭时间,\(sum[i]\)表示打饭时间的前缀和。

\[dp[i][j]=\min(dp[i][j],\max(dp[i-1][j-a[i].a],j+a[i].b))j>=a[i].a\]
\[dp[i][j]=\min(dp[i][j],\max(dp[i-1][j],sum[i]-j+a[i].b))\]

解释一下,其中\(sum[i]-j\)就是另一队的打饭时间,取max是为了考虑上面讲到的两种情况。

看起来有点像背包,实际上不是严格意义上的背包啦。一定要说的话,可以理解成把每个人造成的“影响”放入背包,使得这个影响最小,就能使得整体吃饭时间最少(我口胡的,如果有错望dalao指出)。

代码很短,却蕴含了较大的思维量,也可以说是dp的一贯尿性吧(摊。

参考代码

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<ctime>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#define N 201
#define INF 0x7fffffff
using namespace std;
struct dat{
    int a,b;
}a[N];
int dp[N][N*N],sum[N],n;
inline bool cmp(dat a,dat b)
{
    if(a.b==b.b) return a.a<b.a;
    return a.b>b.b; 
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i) scanf("%d%d",&a[i].a,&a[i].b);
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;++i) sum[i]=sum[i-1]+a[i].a;
    memset(dp,0x3f,sizeof(dp));
    dp[0][0]=0;
    for(int i=1;i<=n;++i)
        for(int j=sum[i];j>=0;--j){
            if(j>=a[i].a) dp[i][j]=min(dp[i][j],max(dp[i-1][j-a[i].a],j+a[i].b));
            dp[i][j]=min(dp[i][j],max(dp[i-1][j],sum[i]-j+a[i].b));
        }
    int ans=INF;
    for(int i=0;i<=sum[n];++i) ans=min(dp[n][i],ans);
    cout<<ans<<endl;
    return 0;
}

其实如果数据更严格,\(N^3\)应该也是过不了的,题解有大佬把它压缩了一下,然鹅我。。。我没看懂(哭。

猜你喜欢

转载自www.cnblogs.com/DarkValkyrie/p/11363202.html