洛谷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)

轉移方程如果沒有看懂,可以結合圖來理解

完整代碼:

#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\n", ans);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章