[ZJOI2005]午餐

Posted akura

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[ZJOI2005]午餐相关的知识,希望对你有一定的参考价值。

题目描述

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

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

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

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

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

输入格式

第一行一个整数N,代表总共有N个人。

以下N行,每行两个整数 Ai,Bi。依次代表第i个人的打饭时间和吃饭时间。

输出格式

一个整数T,代表所有人吃完饭的最早时刻。

所有输入数据均为不超过200的正整数。


容易想到的一个贪心策略就是:让打饭慢的人先打饭。所以我们一开始可以对所有人按照打饭时间排序。

然后我们开始考虑窗口的选择。显然,前i个人的最优解一定是由前i-1个人的最优解更新而来的,所以我们可以考虑用动态规划。考虑第i个人怎么选窗口会最优。

第i个人选窗口会影响到的因素就是:在1窗口打饭一共用了多少时间、在2窗口打饭一共用了多少时间。那么我们把这两个量设计进状态即可。设dp[i][j][k]表示前i个人在1窗口用了j个单位时间、在2窗口用了k个单位时间的情况下,前i个人吃完饭的最早时刻。

那么转移很好想,O(1)转移即可。但是进一步分析会发现一个问题:j和k的上限为\(200*200=40000\),这样开数组肯定会爆的...那么想一想优化。

可以发现j和k加起来刚好等于前i个人的打饭时间和。那么换个思路,我们求出前i个人的打饭时间和sum[i],那么我们只需要知道j就可以求出k了。那么我们的状态就可以省掉一维:dp[i][j]表示前i个人在1号窗口用了j个单位时间的情况下,前i个人吃完饭的最早时刻。可以得出转移方程:

\[ dp[i][j]= \left\ \beginarrayrcl min(max(dp[i-1][j-p[i].a],j+p[i].b),max(dp[i-1][j],k+p[i].b)) & j,k>=a[i]\max(dp[i-1][j-p[i].a],j+p[i].b) & j>=a[i]\max(dp[i-1][j],k+p[i].b) & k>=a[i] \endarray\right. \]

初始化\(dp[i][j]=inf\)\(dp[0][0]=0\)。答案就是\(Max\dp[n][i]\\)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define maxn 201
#define maxm 40001
#define inf 0x3f3f3f3f
using namespace std;

inline int read()
    register int x(0),f(1); register char c(getchar());
    while(c<'0'||'9'<c) if(c=='-') f=-1; c=getchar(); 
    while('0'<=c&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
    return x*f;


struct node int a,b; p[maxn];
int dp[maxn][maxm];
int n,c[maxn];

inline bool cmp(const node &x,const node &y) return x.b>y.b; 
int main()
    n=read();
    for(register int i=1;i<=n;i++) p[i].a=read(),p[i].b=read();
    
    sort(p+1,p+1+n,cmp);
    for(register int i=1;i<=n;i++) c[i]=c[i-1]+p[i].a;

    memset(dp,0x3f,sizeof dp),dp[0][0]=0;
    for(register int i=1;i<=n;i++)
        for(register int j=0;j<=c[i];j++)
            register int k=c[i]-j;
            if(j>=p[i].a && k>=p[i].a) dp[i][j]=min( max( dp[i-1][j-p[i].a],j+p[i].b ),max( dp[i-1][j],k+p[i].b ) );
            else if(j>=p[i].a) dp[i][j]=max( dp[i-1][j-p[i].a],j+p[i].b );
            else if(k>=p[i].a) dp[i][j]=max( dp[i-1][j],k+p[i].b );
        
    

    int ans=inf;
    for(register int i=0;i<=c[n];i++) ans=min( ans,dp[n][i] );
    printf("%d\n",ans);
    return 0;

时间复杂度为\(O(N*40000)\)

以上是关于[ZJOI2005]午餐的主要内容,如果未能解决你的问题,请参考以下文章

Luogu P2577 [ZJOI2005]午餐(dp)

[ZJOI2005] 午餐

[ZJOI2005]午餐

ZJOI2005午餐

[ZJOI2005]午餐 (贪心,动态规划)

P2577 [ZJOI2005]午餐