01背包入门题集
整理一下我之前做过的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; } }