大佬讲解
我再说一下我对二进制以下的理解。
Q:所谓将num拆分成k个二进制数,那么为什么这些二进制数可以表示1~num的任意一个数?
A:同拆分成num个01背包一样,它的原理是由num个1,每一个代表一个物体,用这些物体来表示num个物体的情况。那拆分成k个二进制数不也同样的意思这k个二进制数通过组合一样能表示1~num的每一数。那么就不会漏掉任何一种情况了。
模板代码
#include <iostream>
#include <cstring>
#include <cstdio>
#define INF 0x3f3f3f3f // 用于完全背包初始化
#define MAX(a,b) a>b?a:b
using namespace std;
const int MAXN = 100050;
bool dp[MAXN];
int val[MAXN]; /// 硬币价值
int num[MAXN]; /// 硬币数量
int n, m, sum;
void ZeroPack(int v) { /// 01背包
for(int i = m; i >= v; i--) {
dp[i] |=dp[i - v];
}
}
void CompletePack(int v) { /// 完全背包
for(int i = v; i <= m; i++) {
dp[i] |= dp[i - v];
}
}
void MutiplePack(int v, int num) {///对于1-num的每个数,我们都可以用拆分成的二进制数将其组合起来。
if(v * num >= m) { /// 当这个硬币价值*数量大于m的时候可以看作硬币数量为无限个
CompletePack(v);
} else {
int k = 1;
while(k < num) { /// 二进制优化
ZeroPack(k * v);///每个都单独成一个01背包
num -= k;
k *= 2;
}
if(num>0)
ZeroPack(num * v); /// 再算一下剩余的num单独成一个01背包
}
}
int main() {
while(~scanf("%d%d", &n, &m)) {
if(n == 0 && m == 0) break;
sum = 0;
for(int i = 1; i <= n; i++) {
scanf("%d", &val[i]);
}
for(int i = 1; i <= n; i++) {
scanf("%d", &num[i]);
}
memset(dp, 0, sizeof(dp)); /// 完全背包初始化
dp[0] = 1; /// 背包容量为0时符合情况
for(int i = 1; i <= n; i++) {
MutiplePack(val[i], num[i]);
}
for(int i = 1; i <= m; i++) {
if(dp[i] > 0) sum++; /// 当背包被定义说明情况存在
}
cout << sum << endl;
}
return 0;
}