标签:
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3236
这道题目属于那种
一看:吓尿了
再看:诶 可以做
一做:跪的不行不行的...
两个背包
相当于二维花费 但是每次只需要花1种
这次我的dp数组从一开始就想对了
dp[j][k][i]表示第一个背包装了j且第二个背包装了k且用了(1-i)张免费券后能得到的最大的开心值
我参考了dango的做法
先算把特殊物品取了 在这个过程中得到三维dp数组的所有合法状态【如果特殊物品没取完 那么结果是dp数组的所有位置均为-1】
然后再根据合法状态去取正常的物品
算两个背包【都是花费】和枚举免费的物品这三个过程一定不能分开
否则不能保证每件物品只取一次
算两种物品【不同的物品】一定要分开
【取那些必须取的东西不是背包问题】
【背包问题的机制在于一个【不断更新】 然而必取物品是不允许被“更新掉”的】
几个启发
一
假设费用是1维的
背包九讲中的思路是:
dp[i] = max(dp[i], dp[i - cost] + weight)
其实换个角度 我们也可以改为:
dp[i+cost] = max(dp[i + cost], dp[i] + weight)
这两种写法是等效的 做题的时候要根据题意灵活变通 不要把自己写绕了
【若转移方程中原状态有约束 那么采用第二种写法会比较直观】
二
dp数组是有更新顺序的
由于采用的写法省去了最高维(表示“前i个物品中”)
所以特别要注意改制的问题
复杂的递推式要在最后检查
【如果当前改变了dp[j][k][l]的值 必须确保下面过程中dp[j][k][l]不再作为判断条件或出现在等号右边】
以这道题为例
不管是算特殊物品还是普通物品
免费的哪一栏一定要放最后
哪怕一开始有点想不明白 最后检查的时候 应当看到
dp[j][k][1]的值在下面不仅作为判断条件 还出现在等号右边
换言之
【dp[j][k][1]已经更新为当前状态的值了 我们却在下面的计算中仍旧把它当做是上一个状态的值】
三
【转移方程中 原状态有约束的情况下 初始化一定要把除起始状态外的所有的状态都设为不合法】
#include <cstdio> #include <cstdlib> #include <ctime> #include <iostream> #include <cmath> #include <cstring> #include <algorithm> #include <stack> #include <set> #include <queue> #include <vector> using namespace std; typedef long long ll; const int maxn = 35005; const int maxm = 10010; const int INF = 100000; int v1, v2, n; int dp[510][60][2]; int p[310], h[310], s[310]; void special() { int total = 0; for(int i = 0; i < n; i++) { if(s[i] != 1) continue; int pre_total = total; total += h[i]; for(int j = v1; j >= 0; j--) { for(int k = v2; k >= 0; k--) { if(dp[j][k][1] == pre_total) { if(j + p[i] <= v1) dp[j + p[i]][k][1] = dp[j][k][1] + h[i]; if(k + p[i] <= v2) dp[j][k + p[i]][1] = dp[j][k][1] + h[i]; } if(dp[j][k][0] == pre_total) { dp[j][k][1] = dp[j][k][0] + h[i]; if(j + p[i] <= v1) dp[j + p[i]][k][0] = dp[j][k][0] + h[i];// = total; if(k + p[i] <= v2) dp[j][k + p[i]][0] = dp[j][k][0] + h[i]; } } } } for(int j = v1; j >= 0; j--) for(int k = v2; k >= 0; k--) for(int i = 0; i <= 1; i++) if(dp[j][k][i] != total) dp[j][k][i] = -1; } int normal() { int ans = -1; for(int i = 0; i < n; i++) { if(s[i] != 0) continue; for(int j = v1; j >= 0; j--) { for(int k = v2; k >= 0; k--) { if(dp[j][k][0] != -1) { if(j + p[i] <= v1 && dp[j][k][0] + h[i] > dp[j + p[i]][k][0]) dp[j + p[i]][k][0] = dp[j][k][0] + h[i]; if(k + p[i] <= v2 && dp[j][k][0] + h[i] > dp[j][k + p[i]][0]) dp[j][k + p[i]][0] = dp[j][k][0] + h[i]; } if(dp[j][k][1] != -1) { if(j + p[i] <= v1 && dp[j][k][1] + h[i] > dp[j + p[i]][k][1]) dp[j + p[i]][k][1] = dp[j][k][1] + h[i]; if(k + p[i] <= v2 && dp[j][k][1] + h[i] > dp[j][k + p[i]][1]) dp[j][k + p[i]][1] = dp[j][k][1] + h[i]; } if(dp[j][k][0] != -1 && dp[j][k][0] + h[i] > dp[j][k][1]) dp[j][k][1] = dp[j][k][0] + h[i]; } } } for(int j = v1; j >= 0; j--) for(int k = v2; k >= 0; k--) for(int i = 0; i <= 1; i++) ans = max(ans, dp[j][k][i]); return ans; } int main() { //freopen("in.txt", "r", stdin); int kase = 0; while(scanf("%d%d%d", &v1, &v2, &n) == 3 && v1 != 0) { memset(dp, -1, sizeof(dp)); dp[0][0][0] = 0; for(int i = 0; i < n; i++) scanf("%d%d%d", &p[i], &h[i], &s[i]); printf("Case %d: ", ++kase); special(); int ans = normal(); printf("%d\n\n", ans); } return 0; }
hdu 3236 Gift Hunting 01背包中有两个背包有两种物品+1次的免费券
标签:
原文地址:http://www.cnblogs.com/dishu/p/4296606.html