1. 程式人生 > >Luogu P2577 [ZJOI2005]午餐(dp)

Luogu P2577 [ZJOI2005]午餐(dp)

題面

題目描述

上午的訓練結束了, \(THU \ ACM\) 小組集體去吃午餐,他們一行 \(N\) 人來到了著名的十食堂。這裡有兩個打飯的視窗,每個視窗同一時刻只能給一個人打飯。由於每個人的口味(以及胃口)不同,所以他們要吃的菜各有不同,打飯所要花費的時間是因人而異的。另外每個人吃飯的速度也不盡相同,所以吃飯花費的時間也是可能有所不同的。

\(THU \ ACM\) 小組的吃飯計劃是這樣的:先把所有的人分成兩隊,並安排好每隊中各人的排列順序,然後一號隊伍到一號視窗去排隊打飯,二號隊伍到二號視窗去排隊打飯。每個人打完飯後立刻開始吃,所有人都吃完飯後立刻集合去六教地下室進行下午的訓練。

現在給定了每個人的打飯時間和吃飯時間,要求安排一種最佳的分隊和排隊方案使得所有人都吃完飯的時間儘量早。

假設 \(THU \ ACM\) 小組在時刻 \(0\) 到達十食堂,而且食堂裡面沒有其他吃飯的同學(只有打飯的師傅)。每個人必須而且只能被分在一個隊伍裡。兩個視窗是並行操作互不影響的,而且每個人打飯的時間是和視窗無關的,打完飯之後立刻就開始吃飯,中間沒有延遲。

現在給定 \(N\) 個人各自的打飯時間和吃飯時間,要求輸出最佳方案下所有人吃完飯的時刻。

輸入輸出格式

輸入格式:

第一行一個整數 \(N\) ,代表總共有 \(N\) 個人。

以下 \(N\) 行,每行兩個整數 \(A_i\)\(B_i\) 。依次代表第 \(i\) 個人的打飯時間和吃飯時間。

輸出格式:

一個整數 \(T\)

,代表所有人吃完飯的最早時刻。

輸入輸出樣例

輸入樣例:

5
2 2
7 7
1 3
6 4
8 5

輸出樣例:

17

說明

所有輸入資料均為不超過 \(200\) 的正整數。

思路

\(alec\)\(AC\) 了,教我做吧! --Uranus 我看的題解。 --alec

alec巨佬學習動規中...

首先貪心地思考這道題:如果只有一個視窗,不同打飯時間和吃飯時間的同學要來吃飯,那麼所有人都吃完飯的最早時刻怎麼求?很容易發現,我們儘量使吃飯最慢的人早打飯,可以提高效率。所以先按照吃飯時間將同學們排序。(不是按照先來後到順序,真不公平)

那麼如何來動規呢?考慮設計狀態 \(f[i][j][k]\)

表示前 \(i\) 個同學全部吃上飯,第一個視窗打飯時間為 \(j\) ,第二個視窗打飯時間為 \(k\) 時所有人都吃完飯的最早時刻。

很快發現, \(j+k= \Sigma ^{i}_{p=1}a[p]\) ,而對於每一個 \(i\)\(\Sigma ^{i}_{p=1}a[p]\) 是固定的。所以我們可以統計字首和,消掉狀態的第三維,進行轉移。

同理,我們也可以通過知道 \(j\)\(k\) ,直接計算出 \(i\) ,以此為依據消去第一維,但是此寫法過於毒瘤。

轉移按照以下方法:

for(int i=1;i<=n;i++)
    for(int j=0;j<=sum[i];j++)//sum為統計的字首和
    {
        if(node[i].a<=j) f[i][j]=min(f[i][j],max(f[i-1][j-node[i].a],j+node[i].b));//在一號視窗打飯
        f[i][j]=min(f[i][j],max(f[i-1][j],sum[i]-j+node[i].b));//在二號視窗打飯
    }

那麼我們就可以順利 \(AC\) 了。

AC程式碼

#include<bits/stdc++.h>
using namespace std;
int n,ans=INT_MAX,f[205][40005],sum[205];
struct Node
{
    int a,b;
    bool operator < (const Node &sjf) const {return b>sjf.b;}
}node[205];
int read()
{
    int re=0;
    char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) re=(re<<3)+(re<<1)+ch-'0',ch=getchar();
    return re;
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++) node[i].a=read(),node[i].b=read();
    sort(node+1,node+n+1);
    for(int i=1;i<=n;i++) sum[i]=sum[i-1]+node[i].a;
    memset(f,0x3f,sizeof f);
    f[0][0]=0;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=sum[i];j++)
        {
            if(node[i].a<=j) f[i][j]=min(f[i][j],max(f[i-1][j-node[i].a],j+node[i].b));
            f[i][j]=min(f[i][j],max(f[i-1][j],sum[i]-j+node[i].b));
        }
    for(int i=0;i<=sum[n];i++) ans=min(ans,f[n][i]);
    printf("%d",ans);
    return 0;
}