题目链接:http://poj.org/problem?id=2184
题目大意:给定n头牛,每头有属性智商和幽默感,这两个属性值有正有负,现在要从这n头牛中选出若干头使得他们的智商和与幽默感和不为负数,并且两者两家和最大,如果无解输出0,n<=100,-1000<val<1000.
解题思路:变形的01背包,其实问题的本质是保证智商和幽默感和不为负数情况下的最大和。如果一个为负数那肯定不符合情况,我们设智商属性为费用,幽默感属性为价值,问题转换为求费用大等于0时的费用、价值总和。为什么能够转换呢?因为当智商和为x的时候,可能有若干个幽默感和与之对应,但我们只选择一个最大值,所以动态维护这个x对应的最大幽默感和就行了,和上面说的背包模型等价。
由于本题的费用可能为负数,虽然最后的结果中不能有费用和为负数的情况,但中间的状态可能为负数,而负数的转移过程和正数的方向相反,要分开写。本题我采用Hash的办法,把负数映射到大于20万的地方,其实10万就可以了,但是在允许的情况开大点保险。
状态转移方程:dp[j] = max(dp[j],dp[j-cost[i]+val[i]) (1<=i<=n,sumpos<=j<=sumneg),复杂度O(N*V)
测试数据:
1
代码:
#include <stdio.h> #include <string.h> #define MIN 110 #define MAX 210000 #define INF 1000000000 #define max(a,b) (a)>(b)?(a):(b) int ans,cost[MIN],val[MIN]; int n,sumpos,sumneg,dp[MAX*3]; int GetHash(int x) { if (x < 0) return MAX - x; else return x; } int main() { int i,j,k,tpj,tpk; while (scanf("%d",&n) != EOF) { sumpos = sumneg = 0; for (i = 1; i <= n; ++i) { scanf("%d%d",&cost[i],&val[i]); if (cost[i] > 0) sumpos += cost[i]; else sumneg += cost[i]; } for (i = sumneg; i <= sumpos; ++i) dp[GetHash(i)] = -INF; ans = -INF,dp[0] = 0; for (i = 1; i <= n; ++i) { if (cost[i] >= 0) { for (j = sumpos; j >= sumneg + cost[i]; --j){ tpj = GetHash(j); tpk = GetHash(j-cost[i]); if (dp[tpk] != -INF) dp[tpj] = max(dp[tpj],dp[tpk]+val[i]); } } else { for (j = sumneg; j <= sumpos + cost[i]; ++j) { tpj = GetHash(j); tpk = GetHash(j-cost[i]); if (dp[tpk] != -INF) dp[tpj] = max(dp[tpj],dp[tpk]+val[i]); } } } for (i = 0; i <= sumpos; ++i) if (dp[i] >= 0) ans = max(ans,dp[i]+i); printf("%d\n",ans); } }
本文ZeroClock原创,但可以转载,因为我们是兄弟。