题目传送门: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; }