一种物品,限制了数量,那就不能按照完全背包的写法去写了
如果按照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; }