标签:
1 8 2 2 100 4 4 100 2
400
多重背包,下面的分析参照背包九讲
方法一:转化为01背包
n[i] 表示第 i 种物品的个数,V 为背包容量
把第 i 种物品换成 n[i] 件01背包中的物品,则得到了物品数为 ∑n[i] 的01背包问题,直接求解,复杂度是O(V * ∑n[i])。
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int MAXN = 100 + 10; int pri[MAXN], wei[MAXN], num[MAXN]; int dp[MAXN], t, sum, n; void solve() { scanf("%d", &t); while (t--) { memset(dp, 0, sizeof(dp)); scanf("%d%d", &sum, &n); for (int i = 1; i <= n; i++) scanf("%d%d%d", &pri[i], &wei[i], &num[i]); for (int i = 1; i <= n; i++) ///n种大米 { for (int j = 1; j <= num[i]; j++) ///第i种大米的袋数 { for (int k = sum; k >= pri[i]; k--) ///背包容量 { dp[k] = max(dp[k], dp[k - pri[i]] + wei[i]); ///状态转移方程 } } } printf("%d\n", dp[sum]); } } int main() { solve(); return 0; }
这里用二进制的思想,我们把第 i 种物品换成若干件物品,使得原问题中第 i 种物品可取的每种策略——取1,2...n[i] 件
——均能等价于取若干件代换以后的物品。另外,取超过 n[i] 件的策略必不能出现。
将第 i 种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数,
使这些系数分别为 1,2,4,...,2^(k-1),n[i]-2^k+1,且 k 是满足n[i]-2^k+1>0的最大整数。例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6 的四件物品。
分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第 i 种物品,另外这种方法也能保证对于1..n[i]间的每一个整数,均可以用若干个系数的和表示,这个证明可以分1..2^k-1和2^k..n[i]两段来分别讨论得出,并不难,希望你自己思考尝试一下。
这样就将第 i 种物品分成了O(log n[i])种物品,将原问题转化为了复杂度为O(V*∑log n[i])的01背包问题,是很大的改进。
#include <cstdio> #include <cstring> #include <iostream> using namespace std; const int MAXN = 100 + 10; int pri[MAXN], wei[MAXN], num[MAXN]; int dp[MAXN], t, sum, n; void zeroOnePack(int price, int weight, int sum) ///01背包 { for (int i = sum; i >= price; i--) dp[i] = max(dp[i], dp[i - price] + weight); } void completePack(int price, int weight, int sum) ///完全背包 { for (int i = price; i <= sum; i++) dp[i] = max(dp[i], dp[i - price] + weight); } void multiPack(int n, int sum) ///多重背包 { memset(dp, 0, sizeof(dp)); for (int i = 1; i <= n; i++) { if (pri[i] * num[i] > sum) ///只买第i种大米可以将经费花完 completePack(pri[i], wei[i], sum); ///转化为完全背包 else { for (int k = 1; k < num[i]; k *= 2) ///转化为01背包,系数为1,2,4...2^(k-1) { zeroOnePack(k * pri[i], k * wei[i], sum); num[i] -= k; } if (num[i] != 0) ///最后剩余的袋数(num[i]-2^k+1) zeroOnePack(num[i] * pri[i], num[i] * wei[i], sum); } } printf("%d\n", dp[sum]); } void solve() { scanf("%d", &t); while (t--) { scanf("%d%d", &sum, &n); for (int i = 1; i <= n; i++) scanf("%d%d%d", &pri[i], &wei[i], &num[i]); multiPack(n, sum); } } int main() { solve(); return 0; }由于本题的数据不算大,这两种方法的耗时没有太大差距。
1 8 2 2 100 4 4 100 2
400
多重背包,下面的分析参照背包九讲
方法一:转化为01背包
n[i] 表示第 i 种物品的个数,V 为背包容量
把第 i 种物品换成 n[i] 件01背包中的物品,则得到了物品数为 ∑n[i] 的01背包问题,直接求解,复杂度是O(V * ∑n[i])。
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int MAXN = 100 + 10; int pri[MAXN], wei[MAXN], num[MAXN]; int dp[MAXN], t, sum, n; void solve() { scanf("%d", &t); while (t--) { memset(dp, 0, sizeof(dp)); scanf("%d%d", &sum, &n); for (int i = 1; i <= n; i++) scanf("%d%d%d", &pri[i], &wei[i], &num[i]); for (int i = 1; i <= n; i++) ///n种大米 { for (int j = 1; j <= num[i]; j++) ///第i种大米的袋数 { for (int k = sum; k >= pri[i]; k--) ///背包容量 { dp[k] = max(dp[k], dp[k - pri[i]] + wei[i]); ///状态转移方程 } } } printf("%d\n", dp[sum]); } } int main() { solve(); return 0; }
这里用二进制的思想,我们把第 i 种物品换成若干件物品,使得原问题中第 i 种物品可取的每种策略——取1,2...n[i] 件
——均能等价于取若干件代换以后的物品。另外,取超过 n[i] 件的策略必不能出现。
将第 i 种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数,
使这些系数分别为 1,2,4,...,2^(k-1),n[i]-2^k+1,且 k 是满足n[i]-2^k+1>0的最大整数。例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6 的四件物品。
分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第 i 种物品,另外这种方法也能保证对于1..n[i]间的每一个整数,均可以用若干个系数的和表示,这个证明可以分1..2^k-1和2^k..n[i]两段来分别讨论得出,并不难,希望你自己思考尝试一下。
这样就将第 i 种物品分成了O(log n[i])种物品,将原问题转化为了复杂度为O(V*∑log n[i])的01背包问题,是很大的改进。
#include <cstdio> #include <cstring> #include <iostream> using namespace std; const int MAXN = 100 + 10; int pri[MAXN], wei[MAXN], num[MAXN]; int dp[MAXN], t, sum, n; void zeroOnePack(int price, int weight, int sum) ///01背包 { for (int i = sum; i >= price; i--) dp[i] = max(dp[i], dp[i - price] + weight); } void completePack(int price, int weight, int sum) ///完全背包 { for (int i = price; i <= sum; i++) dp[i] = max(dp[i], dp[i - price] + weight); } void multiPack(int n, int sum) ///多重背包 { memset(dp, 0, sizeof(dp)); for (int i = 1; i <= n; i++) { if (pri[i] * num[i] > sum) ///只买第i种大米可以将经费花完 completePack(pri[i], wei[i], sum); ///转化为完全背包 else { for (int k = 1; k < num[i]; k *= 2) ///转化为01背包,系数为1,2,4...2^(k-1) { zeroOnePack(k * pri[i], k * wei[i], sum); num[i] -= k; } if (num[i] != 0) ///最后剩余的袋数(num[i]-2^k+1) zeroOnePack(num[i] * pri[i], num[i] * wei[i], sum); } } printf("%d\n", dp[sum]); } void solve() { scanf("%d", &t); while (t--) { scanf("%d%d", &sum, &n); for (int i = 1; i <= n; i++) scanf("%d%d%d", &pri[i], &wei[i], &num[i]); multiPack(n, sum); } } int main() { solve(); return 0; }由于本题的数据不算大,这两种方法的耗时没有太大差距。
HDU 2191 悼念512汶川大地震遇难同胞——珍惜现在,感恩生活 多重背包
标签:
原文地址:http://blog.csdn.net/houheshuai/article/details/51361340