链接
题意
给出6种硬币,价值分别为[1, 6],每种硬币有xi个,问是否能将这些硬币分为两组,每组硬币的价值为总价值的一半。
题解
是一种简化版的多重背包问题。
先考虑这样一个问题,给出n个物品,每个物品占用空间为vi,问是否能选择若干物品能填满容量为V的背包。
这是一个01背包问题,但是没价值,只有容量,怎么办?
考虑将问题“复杂化”,每个物品的价值为1,问装满容量为V的背包需要最多多少件物品,无法装满输出-1。
貌似会做了,01背包的裸题,最开始将除容量d[0]外的非法状态设为-INF,进行转移,如果有解,那么d[V]最终一定是非负的。这里注意要“复杂化”成求“最多”物品,因为非法状态是-INF。
回到原来的问题,现在是n种物品,每种mi个,怎么办?
可以将mi个物品看开,这样就变成了01背包问题,但是代价比较大。
如果能将这mi个物品分成若干组(不能太多),并且这些分组的组合能构造出[1, mi]任意个物品那就好了。
的确有这种分法,将mi分为2^0, 2^1, …, 2^(k-1), mi - 2^k + 1即可。
k是保证最后一项>=0的最大值,可以证明这些数可以构造出[1, mi]中任意一个,代码也很好写,将一个值拆成了logm个,效率很高了。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long lint;
const lint oo = 1e16;
int a[10], kase = 0;
int W[200], V[200], cnt;
lint dp[130000];
int main()
{
while(cin >> a[1])
{
int sum = a[1];
for(int i = 2; i <= 6; i++)
{
scanf("%d", &a[i]);
sum += i * a[i];
}
if(0 == sum) break;
if(sum % 2) { printf("Collection #%d:\nCan't be divided.\n\n", ++kase); continue; }
cnt = 0;
for(int i = 1, t, num; i <= 6; i++)
{
num = a[i], t = 1;
while(num >= t)
{
W[cnt] = t;
V[cnt++] = i * t;
num -= t;
t <<= 1;
}
if(num > 0) { W[cnt] = num; V[cnt++] = i * num; }
}
for(int i = 1; i <= sum / 2; i++)
dp[i] = -oo;
dp[0] = 0;
for(int i = 0; i < cnt; i++)
{
for(int j = sum / 2; j >= 0; j--)
if(j >= V[i]) dp[j] = max(dp[j], dp[j - V[i]] + W[i]);
}
printf("Collection #%d:\n", ++kase);
if(dp[sum / 2] < 0)
printf("Can't be divided.\n\n");
else
printf("Can be divided.\n\n");
}
return 0;
}