博弈论+指鹿为马DP法(CSU 2095: Sweet War题解)

题目传送门:CSU 2095:Sweet War

题目大意:皮皮王和嘤嘤怪是好朋友,一天她俩出去玩,皮皮王发现了一个一端开口的玻璃管(大概试管那样的)。玻璃管里有好多巧克力豆一个挨着一个地排着。皮皮王和嘤嘤怪给每个巧克力豆估算了一个美味值s[i]和营养值r[i],她俩都想吃到的巧克力的美味值总和最大(巧克力从开口的一端向封闭的一端依次编号1,2,...,n,只能按顺序一个一个地吃)。于是,她俩就打算玩游戏,两个人轮流进行操作,规则如下:

(1)每个人有一个初始饱食度A,B

(2)每个人轮到自己的时候有两种操作:吃了巧克力豆(饱食度增加那个巧克力豆的营养值),或者直接pass(饱食度下降1,饱食度为0不能pass)

(3)皮皮王先手,皮皮王和嘤嘤怪都特别聪明,能够保证自己每一步都是最优的~

(4)巧克力豆吃完游戏结束

问:皮皮王和嘤嘤怪吃到的巧克力豆的美味度总和

数据范围:0<=A,B,r[i]<=1e9,1<=N<=150,0<=s[i] and ∑s[i] <= 150

分析:

额,这一题我是看VJ上ACMer分享的代码的——菜到做不出来。。写博客的目的是发现这一题DP思路很奇妙啊,别人代码看一遍,醍醐灌顶。我称之为“指鹿为马DP法”(当然,这是我瞎编的词)——通常,我们一个dp数组,求出来之后,其中某一个或者多个值会是我们需要的最终结果,或者再对它们进行少量运算得到最终结果。然而,这一题比较奇特的是,最终结果不是从dp值里选取,而是在dp数组的索引里面选取(就很骚~)。由于以前没做过这种题,直接印象就是觉得饱食度应该作为索引,而美味度应该作为dp值,饱食度范围是1e9,这数组根本没法开,没法做。

言归正传,首先,我们必须认清一个事实,某个人要进行pass操作时,那么前提是她的饱食度更高(由于两个人都非常聪明,那么pass必定是为了吃到下一个更优,如果想要pass的人饱食度比较低,那么另一个人也会选择pass,两个人一起消耗饱食度,饱食度低的肯定就耗不过了)。也就是说,两个人的实际饱食度我们并不关心,只关心二人饱食度的差值。现在我们以皮皮王为主角,以dp数组表示皮皮王的饱食度减去嘤嘤怪的饱食度。dp[i][j][k]表示从i开始操作直到结束,吃到j饱食度所需要的最小相对饱食度,k表示当前轮到谁进行操作了,0指代皮皮王,1指代嘤嘤怪。那么关键的来了:

                dp[i][j][0] = min(dp[i+1][max(0LL, j-s[i])][1]-r[i], max(dp[i+1][j][0]+1+r[i], 1LL));
                dp[i][j][1] = max(dp[i+1][j][0] + r[i], min(dp[i][j][0]-1, -1LL));

解释一波:

对于dp[i][j][0]:

(1)dp[i+1][max(0LL, j-s[i])][1]-r[i]表示皮皮王吃掉了这个豆,饱食度增加r[i],那么当前的饱食度就比下一轮低r[i],第二维的max(...)意思很明显,当然要不小于0啊~

(2)dp[i+1][j][0]+1+r[i]表示皮皮王选择了pass,那么自己需要消耗1的饱食度,嘤嘤怪吃了那个巧克力豆,饱食度增加r[i],那么相对的皮皮王饱食度降低r[i],那么皮皮王当前的相对饱食度就需要比下一轮搞1+r[i],至于max(...)就如前面所说,饱食度高的人才有资格去pass,即相对饱食度至少为1才有资格pass

(3)由于皮皮王非常聪明,她当然会在(1)和(2)里选择一个较小的去操作啊,想要以有限的饱食度达到更高的美味度,那就必然需要以更低的饱食度达到相同的美味度啊

对于dp[i][j][1]:

max()括号里的就不解释了,和上面同理,但为啥前面是min而这里是max呢?——现在轮到嘤嘤怪操作了啊,她想自己达到结果更优,当然会在吃与pass之间选择需要使dp值大的啊,这样皮皮王想吃同样的美味度,就需要更多的饱食度了。

dp求完之后,遍历dp[1][j][0],表示皮皮王从第一个开始吃,满足当前饱食度的最大的j,这就是她所能达到的最大美味度

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<string>
#include<cstring>
#include<vector>
#define ll long long

using namespace std;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int main()
{
    #ifdef AFei
    freopen("in.c", "r", stdin);
    #endif // AFei
    ll n, A, B;
    ll r[152], s[152];
    ll dp[152][152][2];
    while(~scanf("%lld%lld%lld", &n, &A, &B))
    {
        ll sum = 0;
        memset(dp, 0x3f, sizeof dp);
        for(int i = 1; i <= n; ++ i)
        {
            scanf("%lld%lld", &r[i], &s[i]);
            sum += s[i];
        }
        dp[n+1][0][0] = dp[n+1][0][1] = -INF;
        for(int i = n; i; -- i)
        {
            for(int j = 0; j <= sum; ++ j)
            {
                dp[i][j][0] = min(dp[i+1][max(0LL, j-s[i])][1]-r[i], max(dp[i+1][j][0]+1+r[i], 1LL));
                dp[i][j][1] = max(dp[i+1][j][0] + r[i], min(dp[i][j][0]-1, -1LL));
            }
        }
        ll ans = 0;
        for(int i = sum; i >= 0; -- i)
        {
            if(dp[1][i][0] <= A-B)
            {
                ans = i;
                break;
            }
        }
        printf("%lld %lld\n", ans, sum-ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Q1410136042/article/details/80340491
今日推荐