题解 P2577 【[ZJOI2005]午餐】贪心 + DP

为什么贪心是正确的

现在假设有两个相邻的人 a a a b b b,他们的打饭时间分别是 b u y [ a ] buy[a] buy[a] b u y [ b ] buy[b] buy[b],吃饭时间分别是 e a t [ a ] eat[a] eat[a] e a t [ b ] eat[b] eat[b]
很容易发现不管哪个放在前面,所有人都打完饭的时间都是确定的。

如果把 a a a放在前面,那么所有人吃完饭的时间是
所有人打完饭的时间 + m a x ( e a t [ b ] , e a t [ a ] − b u y [ b ] ) max(eat[b],eat[a] - buy[b]) max(eat[b],eat[a]buy[b])

如果把 b b b放在前面,则为
所有人打完饭的时间 + m a x ( e a t [ a ] , e a t [ b ] − b u y [ a ] ) max(eat[a],eat[b] - buy[a]) max(eat[a],eat[b]buy[a])

如果把 a a a放在前面更优,那么我们可以得到以下的不等式:
m a x ( e a t [ b ] , e a t [ a ] − b u y [ b ] ) < m a x ( e a t [ a ] , e a t [ b ] − b u y [ a ] ) max(eat[b],eat[a] - buy[b]) < max(eat[a],eat[b] - buy[a]) max(eat[b],eat[a]buy[b])<max(eat[a],eat[b]buy[a])

然后我还是不会解啊!
难道要分类讨论吗?

**不等式成立的充要条件就是,不等号右边的两个式子里面,至少有一个既大于左边的第一个式子,又大于左边的第二个式子:
**要么

$ eat[a] > eat[b] ~~~ $ && $ ~~~ eat[a] > eat[a] - buy[b]$

要么

$eat[b]-buy[a] > eat[b] ~~~ $ && $ ~~~ eat[b] - buy[a] > eat[a] - buy[b]$

e a t [ b ] − b u y [ a ] > e a t [ b ] eat[b]-buy[a] > eat[b] eat[b]buy[a]>eat[b]永远不可能成立,舍去)

所以说只有当上面的一组不等式成立时 a a a才应该放在前面,化简也就是 e a t [ a ] > e a t [ b ] eat[a] > eat[b] eat[a]>eat[b]

由于两个队伍最后的结构都会遵循eat值递减的规律,所以可以先对所有人按eat值递减排序,再为他分配队伍。

如何DP

其实也可以看做是一个背包,从 n n n个人中选择部分人,使得最后的指标--------所有人的时间最短。
最基本的思路就是令 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示考虑到第 i i i 个人,A队目前排队时间 j j j 分钟,B队目前排队 k k k 分钟时,所有人吃完饭所需要的时间。

为什么可以这样做?因为对于一个将要新加入队伍的人来讲,前面的人吃饭的时间不会因为他的加入而变化,他加入哪个队伍后只可能是因为他自己吃饭太慢而拖后腿,所以我们判断一下他会不会拖后腿,如果拖了后腿更新答案就可以了。

但是这样内存会不够,所以我们需要进行空间上的优化。
就如上面所说,当考虑到第 i i i个人时,所有人的打饭时间是一定的。
如果我们用一个前缀和 s u m [ i ] sum[i] sum[i] 记录前 i i i 个人打完饭一共需要多长时间,那么有 j + k = = s u m [ i ] j + k == sum[i] j+k==sum[i] , 就是说如果我知道A队的人排队需要多久,那么前缀和
减去他们等待的时间就是B队需要等待的时间。

这样三维的DP就变成二维的啦!

     ~~~~     

实现细节

什么时候需要更新答案?当新人拖后腿的时候。

我们首先考虑把新人加入A队:
这时候有三种可能性:

  1. 新人拖后腿了,A队的等待时间变长了,这时候需要把答案更新为
    w a i t A = t i m e O f B u y i n g D i s h e s + b u y [ i d ] + e a t [ i d ] waitA = timeOfBuyingDishes + buy[id] + eat[id] waitA=timeOfBuyingDishes+buy[id]+eat[id]
  2. 新人吃的挺快的,A队原来的某个人吃的最慢。这时候不需要进行任何更新.
  3. 新人吃的挺快的,B队原来的某个人吃的最慢,同样不需要更新.

把新人加入B队的情况类似。

边界条件是什么?

  1. 首先,这题求最短时间,所以除了 f [ 0 ] [ i ] f[0][i] f[0][i] f f f的其他值应该初始化为正无穷大.
  2. 枚举A队的人排队时间的时候注意不要越界。如果现在的新人是 i i i , 那么排在前面的所有人的排队时间是 s u m [ i − 1 ] sum[i-1] sum[i1], 第二维的A队等待时间应该在 [ 0 , s u m [ i − 1 ] ] [0, sum[i-1]] [0,sum[i1]] 范围内。

AC代码

#include 
#include 
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a

using namespace std;
const int INF = 1e9;

int n;
int buy[205];
int eat[205];
int sum[205];
int ord[205];
int f[205][40005];

bool cmp(int x, int y){
    return eat[x] > eat[y];
}

int main(){
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> buy[i] >> eat[i];
        ord[i] = i;
    }
    sort(ord + 1, ord + 1 + n, cmp);
    for(int i = 1; i <= n; i++)
        sum[i] = sum[i-1] + buy[ord[i]];
    for(int i = 1; i <= n; i++)
        for(int j = 0; j <= sum[n]; j++)
            f[i][j] = INF;

    for(int i = 1; i <= n; i++){
        int id = ord[i];
        for(int j = 0; j <= sum[i-1]; j++){
            if (f[i-1][j] == INF) continue;
            int other = sum[i-1] - j;
            //go to list1
            int wait1 = max(j + buy[id] + eat[id], f[i-1][j]);
            //go to list2
            int wait2 = max(other + buy[id] + eat[id], f[i-1][j]);
            f[i][j+buy[id]] = min(f[i][j+buy[id]], wait1);
            f[i][j] = min(f[i][j], wait2);
        }
    }
    int ans = INF;
    for(int i = 0; i <= sum[n]; i++)
        ans = min(ans, f[n][i]);
    cout << ans << endl;
}

你可能感兴趣的:(题解 P2577 【[ZJOI2005]午餐】贪心 + DP)