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;
}
posted @ 2020-04-08 16:52  shuitiangong  阅读(165)  评论(0编辑  收藏  举报