题目大意:有 n 种硬币,每种硬币有一定的数量a[i]和面值c[i],问你 1 ~ m内的所有面值,哪些面值可以由这些硬币组成。
显然是多重背包问题,多重背包指的是每种物品具有有限个数量的背包问题。可以将每种物品单个拆开变成一个变成0 1背包问题,但这题数据太大这种拆解方法无法通过。更加优秀的方法是以 2进制形式拆解每一种物品,然后变成0 1背包,复杂度为 n ∗ m ∗ l o g ( a [ i ] ) n * m * log(a[i]) n∗m∗log(a[i]),仍然无法通过。还可以用单调队列优化使复杂度降为 n ∗ m n * m n∗m(待补)
由于题目问的是是否存在解,而不是最优解,朴素的背包拆解转移方程为:dp[k] = dp[k] | dp[k - c[i]]
,k 是面值,c[i]表示第i枚硬币的面值。dp[k] = 1的情况有两种:一是在使用第 i 枚硬币之前dp[k] 已经为1,二是使用了第i枚硬币后 dp[k]才为1。
对于dp[k]已经为1的状态k不需要再进行转移计算,需要计算的是使用了第 i枚硬币之后才为 1的状态。
(学习完全背包之前,凭着01背包的记忆我一直以为完全背包的转移需要三维:第一维枚举物品,第二维背包空间,第三位枚举物品个数,上限是当前空间能装的最大个数,这样复杂度太大。学习完全背包之后,我发现它的转移充分利用了过去状态的解,使得转移降了一维,完全背包其实只是多重背包的特例,因为完全背包虽然物品无限但空间有限,每种物品实际上最多能装的个数是可以计算出来的,而不是无限个,这说明某些情况下多重背包也可以考虑像完全背包那样转移,但多重背包需要考虑的不只是能不能装得下,还有该物品的数量,这就需要多记忆一个数组,用来记忆每个状态使用了多少个这种物品,在使用数量不超过物品个数的情况下转移,这题的优化同样可以这样搞,省略一维拆解,复杂度变成O(n * m))
(这种优化只能得到可行解,得不到最优解,也就是说如果要求最优解不能用这种转移方法)
(使用二进制拆解法的多重背包问题)
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
const int maxn = 1e6 + 100;
int num[maxn],c[maxn],n,m;
bool f[maxn];
int main() {
while(~scanf("%d%d",&n,&m) && n && m) {
for(int i = 0; i <= m; i++) f[i] = 0;
for(int i = 1; i <= n; i++)
scanf("%d",&c[i]);
for(int i = 1; i <= n; i++)
scanf("%d",&num[i]);
f[0] = true;
for(int i = 1; i <= n; i++) {
int j;
for(j = 1; j < num[i]; j <<= 1) {
num[i] -= j;
for(int k = m; k >= 0; k--) {
if(k >= j * c[i])
f[k] |= f[k - j * c[i]];
}
}
if(num[i]) {
for(int k = m; k >= 0; k--) {
if(k >= c[i] * num[i])
f[k] |= f[k - num[i] * c[i]];
}
}
}
int ans = 0;
for(int i = 1; i <= m; i++) {
ans += f[i];
}
printf("%d\n",ans);
}
return 0;
}
(只转移不为1的状态,类似完全背包的转移优化)
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
const int maxn = 1e5 + 100;
int num[maxn],c[maxn],n,m,used[maxn];
bool f[maxn];
int main() {
while(~scanf("%d%d",&n,&m) && n && m) {
for(int i = 0; i <= m; i++) f[i] = false;
for(int i = 1; i <= n; i++)
scanf("%d",&c[i]);
for(int i = 1; i <= n; i++)
scanf("%d",&num[i]);
f[0] = true;
for(int i = 1; i <= n; i++) {
for(int j = 0; j <= m; j++) used[j] = 0;
for(int j = c[i]; j <= m; j++) {
if(!f[j] && f[j - c[i]] && used[j - c[i]] < num[i]) {
f[j] = true;
used[j] = used[j - c[i]] + 1;
}
}
}
int ans = 0;
for(int i = 1; i <= m; i++) {
ans += f[i];
}
printf("%d\n",ans);
}
return 0;
}