题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2844
题目意思:有A1,A2,...,An 这 n 种面值的钱,分别对应的数量是C1,C2,...,Cn。问根据这么多数量的钱 能组成多少个 <= m 的钱。
如果用多重背包来做,超时了...如果用记忆化搜索,还是...超时= =.....
这个方法是网上搜的,觉得好神奇,能看懂一些。
它是根据完全背包的思路做的,但是需要限制每种币种的使用数量,于是就多了个used[] 数组来记录了。 ! f[j]表示之前没有组成 j 这么多的钱,f[j] = 1 就表示已经组成过 j 那么多的钱了,下次再遇到同样能构成 j 那么多的钱就不要再重复算了。 这个f[j-A[i]]用得好巧妙,妙在它 j - A[i] 是绝对比 j 小的,如果在不超过C[i] 数量(used[j-A[i]])的情况下,用一定数量的 A[i] 再加上之前用过的 A[i-1] ,A[i-2],...,A[1] (当然也可以不用,也可以用部分) 这些币值能能够组成 j 这么多钱。
这句:used[j] = used[j-A[i]] + 1; 有一点不太懂,不知道为什么要从used[j-A[i]] 前提下 +1......
代码中注释部分可能有误导别人的成分,欢迎指出不足(哎~~~没办法啦,网上的这份代码,好像没有人写过注释,我也是按自己理解啦= =)
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 using namespace std; 5 6 const int maxn = 1e5 + 5; 7 const int N = 100 + 5; 8 9 int A[N], C[N]; 10 int f[maxn], used[maxn]; 11 12 int main() 13 { 14 int n, m; 15 while (scanf("%d%d", &n, &m) != EOF && (m+n)) 16 { 17 memset(f, 0, sizeof(f)); 18 for (int i = 0; i < n; i++) 19 scanf("%d", &A[i]); 20 for (int i = 0; i < n; i++) 21 scanf("%d", &C[i]); 22 f[0] = 1; 23 int ans = 0; 24 for (int i = 0; i < n; i++) 25 { 26 for (int j = 0; j <= m; j++) 27 used[j] = 0; // used[j]表示当前这种面值为j时用了A[i]多少次,它的使用时限制A[i]被使用的数量,否则就可以用无限个了 28 for (int j = A[i]; j <= m; j++) 29 { 30 if (!f[j] && f[j-A[i]] && used[j-A[i]] < C[i]) // 用f[j]表示能组成这价值为j的钱和不能(0与1), f[j-A[i]] 表示之前能组成j-A[i]这么多钱 31 { 32 // printf("!f[j]: !f[%d]; f[j-A[i]]: f[%d]\n", j, j-A[i]); 33 // printf("used[j-A[i]]: used[%d] = %d, C[i]: C[%d] = %d\n", j-A[i], used[j-A[i]], i, C[i]); 34 f[j] = 1; 35 used[j] = used[j-A[i]] + 1; // 组成j这么多钱还需要用多一次A[i]这样的币值 36 ans++; 37 // printf("used[%d] = %d\n", j, used[j]); 38 // printf("组成面值为 %d 时用了 A[%d](即 %d) %d 次\n\n", j, i, A[i], used[j]); 39 } 40 } 41 } 42 printf("%d\n", ans); 43 } 44 return 0; 45 }
输出变量图,真诚奉献,真心强大啊~~~