題目大意:有六種等級的石頭,已知每個等級石頭的個數,求是否存在一種方案,使這些石頭能分成兩堆,滿足這兩堆石頭的等級之和相等。
思路:一開始想要進行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;
}