HDU - 2844 - Coins(多重背包二进制优化)
题目链接
题目大意:给定几种不同面额的硬币若干枚,需要求的用这些硬币可以组成多少种范围在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\n", sum);
}
return 0;
}