洛谷 P2577 [ZJOI2005]午餐

这道题目比较难想。

题解:##

算法:贪心+dp

容易想到贪心:吃饭慢的先打饭节约时间, 所以先将人按吃饭时间从大到小排序。

然后就是dp了:
首先,应该想到f[i][j][k]:前i个人,在1号窗口打饭总时间j,在2号窗口打饭总时间k
当然,这样会爆空间,所以想到去掉一维。
f[i][j]表示前i个人,在1号窗口打饭总时间j,最早吃完饭的时间
我们可以发现 j+k=前i个人打饭总和,k = sum(i)-j。
所以可以维护一个前缀和:

for(int i = 1; i <= n; i++)
	sum[i] = sum[i-1] + s[i].a;

接下来就是dp的转移:

将第i个人放在1号窗口:
if(j>=s[i].a) f[i][j] = min(f[i][j], max(f[i-1][j-s[i].a], j+s[i].b));
f[i-1][j-s[i].a]是i号人打饭+吃饭的时间不足i-1号人吃饭的时间, 所以没有影响
j+s[i].b就是造成了影响

将第i个人放在2号窗口:
f[i][j] = min(f[i][j], max(f[i-1][j], sum[i]-j+s[i].b));   
这里也是一样的 
(sum[i]-j 就是k)

转移方程如果没有看懂,可以结合图来理解

Code:##

#include<bits/stdc++.h>
using namespace std;
const int N = 210;
int f[N][N*N];
struct node
{
    int a, b;
    bool operator <(node z) const
    {
        return b>z.b;
    }
}s[N];
int sum[N];
int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        scanf("%d %d", &s[i].a, &s[i].b);
    sort(s+1, s+1+n);
    for(int i = 1; i <= n; i++)
        sum[i] = sum[i-1] + s[i].a;
    memset(f, 127, sizeof(f));
    f[0][0] = 0;
    for(int i = 1; i <= n; i++)
    {
        for(int j = 0; j <= sum[i]; j++)
        {
            if(j>=s[i].a) f[i][j] = min(f[i][j], max(f[i-1][j-s[i].a], j+s[i].b));
            f[i][j] = min(f[i][j], max(f[i-1][j], sum[i]-j+s[i].b));
        }
    }
    int ans = 2147483647;
    for(int i = 0; i <= sum[n]; i++)
        ans = min(ans, f[n][i]);
    printf("%d
", ans);
    return 0;
}

原文地址:https://www.cnblogs.com/zzy2005/p/9837943.html