题目链接
题目大意:给定几种不同面额的硬币若干枚,需要求的用这些硬币可以组成多少种范围在1~m的不同面额的组合。
题面是很典型的多重背包,但是数据看起来并不是直接用多重背包就能过的。这里需要多重背包的一个优化技巧:二进制优化。我们把一个数(n)拆成(1,2,4,8···)和(k)(拆剩下的数),就可以表示位于区间([1,n])之内的所有数。为什么呢?因为前面的二进制数之和必定大于等于(k),如果(k)大于它们的话就可以拆出来一个更大的二进制数。那么由于前面的二进制数可以表示出([1,n-k])内的数(可以以二进制的角度来理解),所以加上(k)就能表示出([1,n])之内的数了。
所以说我们只要先对之前的物品做一下预处理。对于单个物品,如果其总值不小于拥有的钱数的话,直接当成一个物品用完全背包就行了。如果其总值小于拥有的钱数,就把它用二进制优化拆成若干个物品来跑(01)背包。最后统计出所有的物品能够组成的不同面值的数量就行了。
const int maxn = 1e5+10;
int coin[105], c[105];
bool dp[maxn];
int main(void) {
int n, m;
while(~scanf("%d%d", &n, &m) && (n||m)) {
zero(dp); dp[0] = 1;
for (int i = 1; i<=n; ++i) scanf("%d", &coin[i]);
for (int i = 1; i<=n; ++i) {
scanf("%d", &c[i]);
if (coin[i]*c[i]>=m)
for (int k = coin[i]; k<=m; ++k) //完全背包
dp[k] |= dp[k-coin[i]];
else {
for (int j = 1; c[i]>=j; j<<=1) { //对多重背包进行二进制优化
for (int k = m; k>=coin[i]*j; --k)
dp[k] |= dp[k-coin[i]*j];
c[i]-=j;
}
if (c[i])
for (int k = m; k>=coin[i]*c[i]; --k)
dp[k] |= dp[k-coin[i]*c[i]];
}
}
int sum = 0;
for (int i = 1; i<=m; ++i) sum += dp[i];
printf("%d
", sum);
}
return 0;
}