题目大意:有六种等级的石头,已知每个等级石头的个数,求是否存在一种方案,使这些石头能分成两堆,满足这两堆石头的等级之和相等。
思路:一开始想要进行DFS,不过题目给出石头总数最大会达到20000,所以DFS显然会超时。可以对问题稍微转化一下,首先求出石头的价值总和,如果是奇数显然没有方案。如果是偶数则除以2,记结果为capacity,然后对等级为i的石头视为体积为i,价值为i的物品,并尽量填满capacity;令dp[i]表示体积为i的容器所装载的最大价值,如果dp[v]==v则说明存在方案,否则不存在。
这样就把问题转化为多重背包问题。多重背包问题存在两种情况:
(1)当n[i]*i>=capacity时,则视为完全背包问题,伪代码为:
for j = i to capacity
dp[j] = max (dp[j], dp[j-i]+i)
(2)当n[i]*i<capacity时,将石头分解,视为01背包问题,伪代码为:
integer:k=1,amount=n[i];
while k<amount
begin
for j = capacity to i*k
dp[j] = max (dp[j], dp[j-k*i]+k*i)
amount = amount - k;
k = k * 2;
end;
for j = capacity to i*amount
dp[j] = max(dp[j], dp[j-amount*i]+amount*i);
代码:
#include <iostream>
using namespace std;
int n[7],sum_value,capacity,amount;
int dp[120005];
inline int max2(int a,int b)
{
if (a>b)
return a;
return b;
}
int main()
{
int i,j,k,test_cases=1;
while (scanf("%d%d%d%d%d%d",&n[1],&n[2],&n[3],&n[4],&n[5],&n[6])==6)
{
for (i=1,sum_value=0;i<=6;i++)
sum_value+=n[i]*i;
if (sum_value==0)
break;
if (sum_value%2!=0)
{
printf("Collection #%d:\n",test_cases++);
printf("Can't be divided.\n\n");
continue;
}
capacity=sum_value/2;
memset(dp,0,sizeof(dp));
for (i=1;i<=6;i++)
{
if (n[i]*i>=capacity)
for (j=i;j<=capacity;j++)
dp[j]=max2(dp[j],dp[j-i]+i);
else
{
k=1;
amount=n[i];
while (k<amount)
{
for (j=capacity;j>=i*k;j--)
dp[j]=max2(dp[j],dp[j-k*i]+k*i);
amount-=k;
k*=2;
}
for (j=capacity;j>=i*amount;j--)
dp[j]=max2(dp[j],dp[j-amount*i]+amount*i);
}
}
printf("Collection #%d:\n",test_cases++);
if (dp[capacity]==capacity)
printf("Can be divided.\n\n");
else
printf("Can't be divided.\n\n");
}
return 0;
}