整理一下我之前做过的01背包的基础题目,以后不断更新
PS:限于篇幅,每道题目只给出粗略分析以及核心代码
这题就是赤裸裸的01背包裸题,不过开二维的数组似乎不行,那么一维能节省空间。背包的第二个for循环是倒序的,这是因为dp[i][j]是由dp[i-1][j-v[i]]转移来的。如果顺序的话,是从dp[i][j-v[i]]转移来的,那是完全背包的状态转移方程。
代码:
memset (dp, 0, sizeof (dp));
for (int i=1; i<=n; ++i) {
for (int j=m; j>=v[i]; --j) {
if (dp[j] < dp[j-v[i]] + w[i]) {
dp[j] = dp[j-v[i]] + w[i];
}
}
}
printf ("%d\n", dp[m]);
这题比上一题绕了个弯,求超过b的最小高度。先定背包容量为总高度sum,dp[j] 表示当前容量为j时的最大高度为多少。然后从b开始枚举,当dp[j] >= b时,那么dp[j] - b一定是最小高度差
代码:
int sum = 0;
for (int i=1; i<=n; ++i) {
scanf ("%d", &h[i]);
sum += h[i];
}
for (int i=0; i<=sum; ++i) dp[i] = 0;
for (int i=1; i<=n; ++i) {
for (int j=sum; j>=h[i]; --j) {
if (dp[j] < dp[j-h[i]] + h[i]) {
dp[j] = dp[j-h[i]] + h[i];
}
}
}
int ans = INF;
for (int i=b; i<=sum; ++i) {
if (dp[i] >= b) {
ans = dp[i] - b; break;
}
}
printf ("%d\n", ans);
3. HDOJ 2546 饭卡
首先将m - 5,不考虑最贵的菜(剩下的钱+5可以买下最贵的菜),那么前n-1道菜用01背包,dp[j] 表示用j余额最大的消费,最后处理最贵的菜。
代码:
if (m < 5) {
printf ("%d\n", m);
}
else {
sort (price+1, price+n+1);
int mx = price[n];
m -= 5;
memset (dp, 0, sizeof(dp));
for (int i=1; i<=n-1; ++i) {
for (int j=m; j>=price[i]; --j) {
dp[j] = max (dp[j], dp[j-price[i]] + price[i]);
}
}
printf ("%d\n", m + 5 - dp[m] - mx);
}
4. POJ 1948 Triangular Pastures
首先我们考虑给出3边求面积,用到海伦公式P = (a + b + c) / 2.0; S = sqrt(p * (p - a) * (p - b) * (p - c))。因为每条木板最多只能用到一次,用01背包写~ 二维dp:dp[i][j] = true 表示边i和边j能构成一个三角形,那么第三边就是sum - i - j。其次,在一个三角形中,每条边最大是sum / 2;
代码:
int sum = 0;
for (int i=1; i<=n; ++i) {
scanf ("%d", &l[i]); sum += l[i];
}
int half = sum / 2;
memset (dp, false, sizeof (dp));
dp[0][0] = true;
for (int i=1; i<=n; ++i) {
for (int j=half; j>=0; --j) {
for (int k=j; k>=0; --k) {
if ((j >= l[i] && dp[j-l[i]][k]) || k >= l[i] && dp[j][k-l[i]]) {
dp[j][k] = true;
}
}
}
}
int ans = -1;
for (int i=half; i>=1; --i) {
for (int j=i; j>=1; --j) {
int k = sum - i - j;
if (dp[i][j] && ok (i, j, k)) {
ans = max (ans, area (i, j, k));
}
}
}
printf ("%d\n", ans);
5. POJ 1837 Balance
设f[i][j]代表挂了前i个砝码时,平衡度为j的方法的个数。显然,平衡度的范围为[-25*20*15,25*20*15],即[-7500,7500],但是要使天平保持平衡,一旦平衡度超过3750,或小于-3750,那么剩下的砝码无论如何放,天平都无法平衡,所以这种情况可以不用考虑。当然,实际时,由于不允许有负数,所以f[i][j]需要进行必要的修改。
代码:
memset (dp, 0, sizeof (dp));
dp[0][3750] = 1; //修改成3750为平衡点
for (int i=1; i<=m; ++i) {
for (int j=0; j<=15000; ++j) {
if (dp[i-1][j]) { //从3750开始
for (int k=1; k<=n; ++k) { //可能下垂任何钩的重量,但他是被迫使用所有的重量
dp[i][j+l[k]*w[i]] += dp[i-1][j]; //dp[i][j] 代表挂了前i个砝码时,平衡度为j的方法的个数
}
}
}
}
printf ("%d\n", dp[m][3750]); //输出平衡点的个数
给出num(num<=100)头奶牛的S和F值(-1000<=S,F<=1000),要求在这几头奶牛中选出若干头,使得在其总S值TS和总F值TF均不为负的前提下,求最大的TS+TF值,可以把S当体积,F当价值做01背包。但是注意是S可为负,所以整体加100000,然后要注意DP顺序,S为负是要顺序,为正时逆序。如果上一题会的话,这一题也不难。
代码:
for (int i=0; i<=200000; ++i) dp[i] = -INF;
dp[100000] = 0;
for (int i=1; i<=n; ++i)
{
if (v[i] > 0) {
for (int j=200000; j>=v[i]; --j) {
if (dp[j-v[i]] > -INF)
dp[j] = max (dp[j], dp[j-v[i]] + w[i]);
}
}
else {
for (int j=0; j=200000+v[i]; ++j) {
if (dp[j-v[i]] > -INF)
dp[j] = max (dp[j], dp[j-v[i]] + w[i]);
}
}
}
int ans = 0;
for (int i=100000; i<=200000; ++i) {
if (dp[i] >= 0 && dp[i]+i-100000 > ans)
ans = dp[i] + i - 100000;
}
printf ("%d\n", ans);
概率问题,考虑对立事件,P (拿到offer) = 1 - P (上一次没拿到offer) * P (这次没拿到offer)
代码:
memset (dp, 0, sizeof (dp));
for (int i=1; i<=m; ++i) {
for (int j=n; j>=v[i]; --j) {
dp[j] = max (dp[j], 1 - (1 - dp[j-v[i]]) * (1 - p[i]));
}
}
printf ("%.1lf%%\n", dp[n] * 100);
概率问题,与上一题类似,反向思维
代码:
int sum = 0;
for (int i=1; i<=N; ++i) {
scanf ("%d%f", &m[i], &p[i]);
sum += m[i]; //反向思维, 考虑逃脱概率
}
for (int i=1; i<=sum; ++i) dp[i] = 0;
dp[0] = 1; //没偷钱逃脱的概率
for (int i=1; i<=N; ++i) {
for (int j=sum; j>=m[i]; --j) { //偷钱逃脱的概率
dp[j] = max (dp[j], dp[j-m[i]] * (1 - p[i])); //少写了个d ->成p (/ □ \)
}
}
P = 1 - P;
for (int i=sum; i>=0; --i) {
if (dp[i] >= P) { //寻找大于成功逃脱概率的最大偷钱数
printf ("%d\n", i);
break;
}
}