POJ 2184 ->Cow Exhibition - -
动态规划中的01背包问题。
题意是给两个数组分别表示每只牛的幽默感和智商,求全体牛的一个子集使得子集中所有牛的幽默感和智商和的总和最大,条件是幽默感总和和智商总和不能为负数。如果把幽默感当成重量,智力值当成价值(或者相反)就变成了普通的01背包问题了。但是有一个问题就是值可能出现小于0的情况— —,肿么办?凉拌……可以像本体的主流做法一样开二倍数组把原点移到100000,或者也可以每次计算都把a[i]加上1000,然后单独开一个数组,记录子集的大小,即多用的1000的个数。
采用第二种方法,则如果01背包中的状态转移方程为f[i] = max(f[i], f[i - w[j]] + v[j]),那么本题应该变形为:
if (d[j] - tot[j] * 1000 < d[j - a[i]] + b[i] - (tot[j - a[i]] + 1) * 1000){ d[j] = d[j - a[i]] + b[i]; tot[j] = tot[j - a[i]] + 1; }
其中a,b数组表示幽默感和智商值,d[i]代表幽默感为i-1000*tot[i]时智商的最大值,tot[i]对应i加了多少个1000。
代码:
#include <stdio.h> #include <string.h> #include <iostream> #include <cstring> #include <cmath> using namespace std; /* * a[i],b[i]数组分别表示Si,Fi * d[i]数组表示Si'==i时Fi的最大值,Si'是si经处理后的值 * 因为a[i]可能为负数,所以对每个a[i]加1000 */ const int inf = 1<<30; const int N = 200050; const int M = 101; int d[N], a[M], b[M], tot[N]; int main(){ int n, MAX; while (scanf("%d", &n) != EOF){ MAX = 0; for (int i = 0; i < n; i++){ scanf("%d%d", &a[i], &b[i]); //如果a[i],b[i]都小于0那都不能取 if (a[i] <= 0 && b[i] <= 0){ i--, n--; continue; } a[i] += 1000; MAX += a[i]; } for (int i = 0; i <= MAX; d[i++] = -inf); memset(tot, 0, sizeof(tot)); d[0] = 0; for (int i = 0; i < n; i++) for (int j = MAX; j >= a[i]; j--) //记住是d[j]-tot[j]*1000的最大值,因为这个地方wa了9遍>_< if (d[j] - tot[j] * 1000 < d[j - a[i]] + b[i] - (tot[j - a[i]] + 1) * 1000){ d[j] = d[j - a[i]] + b[i]; tot[j] = tot[j - a[i]] + 1; } int maxf = 0; for (int i = 0; i <= MAX; i++){ if (d[i] >= 0 && i - 1000 * tot[i] >= 0 && maxf < d[i] + i - 1000 * tot[i]){ maxf = d[i] + i - 1000 * tot[i]; } } printf("%d\n", maxf); } return 0; }