POJ_1014 Dividing(多重背包问题)

  这题做了将近一个月,断断续续的看背包问题,今天总于一口气把背包九讲中的前三讲看完了。

这个一个很典型的多重背包问题:第i件物品有n[i]种,所占的容量权值分别是c[i],w[i]。求最大容量为V时所得到的最大权值。按照背包九讲的讲解,可以利用二进制思想,把n[i]个物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为1,2,4,...,2^(k-1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数。例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。(1.....n[i]中的任意一个数都能由若干个系数相加得到,保证了[1, n[i]]区间上任意一个数都是可达的)

为什么这样正确呢?

证明:

    (1) 数列1,2,4,…,2^(k-1),n-2^k+1中所有元素的和为n,所以若干元素的和的范围为:[1, n];

    (2)如果正整数t<= 2^k – 1,则t一定能用1,2,4,…,2^(k-1)中某几个数的和表示,这个很容易证明:我们把t的二进制表示写出来,很明显,t可以表示成 n=a0*2^0+a1*2^1+…+ak*2^(k-1),其中ak=0或者1,表示t的第ak位二进制数为0或者1.

    (3)如果t>=2^k,设s=n-2^k+1,则t-s<=2^k-1,因而t-s可以表示成1,2,4,…,2^(k-1)中某几个数的和的形式,进而t可以表示成1,2,4,…,2^(k-1),s中某几个数的和(加数中一定含有s)的形式。

    (证毕!)

  转成01背包问题之后,将总的marbles的一半看成V,二进制压缩后的序列W[i]即作为c[i],又作为w[i]。状态转移方程为:

for i: 1 .. n

  
for j: sum/2 .. w[i]

    dp[j]
= max(dp[j], dp[j-w[i]] + w[i]);



代码:

#include <iostream>
#include
<cstdio>
#include
<cstring>

using namespace std;
const int N = 120007;
int num[6];
int w[N], dp[N];

int main()
{
//freopen("data.in", "r", stdin);

int cas = 0, i, j;
while(scanf("%d%d%d%d%d%d", &num[0], &num[1], &num[2], &num[3], &num[4], &num[5]) != EOF)
{
if(!num[0] && !num[1] && !num[2] && !num[3] && !num[4] && !num[5])
break;
memset(w,
0, sizeof(w));
memset(dp,
0, sizeof(dp));

int sum = 0;
for(i = 0; i < 6; i++)
sum
+= (i+1)*num[i];

printf(
"Collection #%d:\n", ++cas);
if(sum&1)
{
printf(
"Can't be divided.\n\n");
continue;
}

int k = 0; sum >>= 1;
for(i = 0; i < 6; i++) //利用二进制思想压缩成0-1背包问题
{
if(!num[i]) continue;
int t = 1;
while(t < num[i])
{
w[k
++] = t*(i+1);
num[i]
-= t;
t
<<= 1;
}
w[k
++] = num[i]*(i+1);
}

for(i = 0; i < k; i++)
for(j = sum; j >= w[i]; j--)
dp[j]
= max(dp[j], dp[j-w[i]] + w[i]); //0-1背包

if(dp[sum] == sum) //总数的1/2可达。
printf("Can be divided.\n\n");
else
printf(
"Can't be divided.\n\n");
}
return 0;
}
原文地址:https://www.cnblogs.com/vongang/p/2160665.html