一种物品,限制了数量,那就不能按照完全背包的写法去写了

如果按照01背包的方法去写,如果数量很大,那么必然会超时,那有没有什么好办法呢?


我们先讲神奇的二进制思想

我们都知道,任何一个十进制都能转换成二进制(废话)

那么二进制就会对应着许多二进位(还是废话)

每个二进位都是2的倍数(肯定啊)

所以反过来讲,任何一个数字,都能由2的倍数相加组成(........)


再来看一个二进位111111(2),一共有6个二进位,对应的数字分别是32,16,8,4,2,1

那么,我只用这6个数字,我就能描述出1~63中所有的情况了..

其实道理很明白哇,64的二进制是1000000(2),63的二进制是111111(2)

所以只要小于63的,都能通过那些二进位重新组成这个数字


说二进制思想有什么用呢?假如我们的n等于63,本来如果我用01背包,我需要做63次

如果我用二进制思想去分解n,可以得到32,16,8,4,2,1,那么我只需要拿这6个去做01背包,就能表示出所有的情况了

换句话说,在枚举这里的复杂度,从O(n)降到了O(logn),优化非常明显!

#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
#include<ctime>
#include<functional>
#include<algorithm>

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

const int MX = 5e5 + 5;
const int INF = 0x3f3f3f3f;

int A[10];
bool dp[MX];

void solve(int t, int V) {//01背包
    for(int i = V; i >= t; i--) {
        dp[i] |= dp[i - t];
    }
}

int main() {
    int ansk = 0;
    while(~scanf("%d", &A[1])) {
        memset(dp, 0, sizeof(dp));

        int V = A[1];
        for(int i = 2; i <= 6; i++) {
            scanf("%d", &A[i]);
            V += A[i] * i;
        }
        if(!V) break;

        printf("Collection #%d:\n", ++ansk);
        if(V % 2 != 0) {
            printf("Can't be divided.\n\n");
            continue;
        }

        V /= 2;
        dp[0] = true;
        for(int i = 1; i <= 6; i++) {
            if(!A[i]) continue;

            int t = 1;
            while(t < A[i]) {//分解A[i]
                solve(t * i, V);
                A[i] -= t;
                t <<= 1;
            }
            solve(A[i] * i, V);//肯定有时候不能完全分解,还会剩下一点点,单独做一次01背包
        }
        if(dp[V]) printf("Can be divided.\n\n");
        else printf("Can't be divided.\n\n");
    }
    return 0;
}