题目链接: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
0 1
2
-1 2
2 -2
3
1 2
2 3
3 4
3
-1 -2
-2 -3
-4 -5
5
-5 7
8 -6
6 -3
2 1
-8 -5
代码:
#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原创,但可以转载,因为我们是兄弟。