多重揹包问题的应用

《多重揹包问题的应用》,开始我们先来看看一个经典的算法问题。

1014:Dividing

时间限制: 
1000ms 
内存限制: 
65536kB
描述
Marsha and Bill own a collection of marbles. They want to split the collection among themselves so that both receive an equal share of the marbles. This would be easy if all the marbles had the same value, because then they could just split the collection in half. But unfortunately, some of the marbles are larger, or more beautiful than others. So, Marsha and Bill start by assigning a value, a natural number between one and six, to each marble. Now they want to divide the marbles so that each of them gets the same total value. Unfortunately, they realize that it might be impossible to divide the marbles in this way (even if the total value of all marbles is even). For example, if there are one marble of value 1, one of value 3 and two of value 4, then they cannot be split into sets of equal value. So, they ask you to write a program that checks whether there is a fair partition of the marbles.
输入
Each line in the input file describes one collection of marbles to be divided. The lines contain six non-negative integers n1 , . . . , n6 , where ni is the number of marbles of value i. So, the example from above would be described by the input-line “1 0 1 2 0 0”. The maximum total number of marbles will be 20000. 

The last line of the input file will be “0 0 0 0 0 0”; do not process this line.

输出
For each collection, output “Collection #k:”, where k is the number of the test case, and then either “Can be divided.” or “Can’t be divided.”. 

Output a blank line after each test case.

样例输入
1 0 1 2 0 0 
1 0 0 0 1 1 
0 0 0 0 0 0 
样例输出
Collection #1:
Can't be divided.

Collection #2:
Can be divided.

 

我想很多朋友已看到这个问题,脑海里想的是深搜,穷举,确实这样做很直接。我们先来来看下深搜的方法。

#include <stdio.h>

 int dfs(int i,int *n,int current,int num)
{
    if(current==num) return 1;
    if(current>num) return 0;
    if(i==7) return 0;
    for(int j=0;j<=n[i];j++)
    {
        if(dfs(i+1,n,current+j*i,num)) return 1;
    }
    return 0;
}
int main()
{
    int n[7];
    int group=0;
 while(1)
    {
          int all=0;
        for(int i=1;i<7;i++)
        {
            scanf("%d",&n[i]);
            all+=i*n[i];
        }
    group++;
        if(!all)
            break;
            if (all%2!=0)
        {
            printf("Collection #%d:\n",group);
            printf("Can't be divided.\n\n");
            continue;
        }
        if(dfs(1,n,0,all/2))
        {
              printf("Collection #%d:\n",group);
            printf("Can be divided.\n\n");
            continue;
        }
        else
        {
              printf("Collection #%d:\n",group);
            printf("Can't be divided.\n\n");
            continue;
        }
    }
    return 0;
}

确实这样做是理所当然的,但是很不幸,这样做超时了!我们来看这个题目的本质:有一堆硬币(面值1到6)若干,请问是否能选择部分硬币面值和为总面值的一半?
看到这可能还有些朋友没有回过神来,那么我们再来变下。
有6种物品若干(有限),每种物品的价值分别为1到6,价值为i(1<=i<=6)的物品同时重量为i,先将这些物品放入最大载重为v的包里,请问是否有一种放法,使揹包里物品价值最高,并且揹包刚好装满。


好吧,看到这而,应该都恍然大悟了吧,这其实是一个多重揹包问题,每个物品的数量有限,并且由于每个物品的单位价值相等,因此理论上揹包放满时价值最高,我们只要判断最后揹包是否放满即可。
代码如下:

#include<stdio.h>
int dp[120005];
int V,v;
void bag01(int c,int w)//01揹包
{
    for(v=V;v>=c;v--)
        if(dp[v]<dp[v-c]+w)
        dp[v]=dp[v-c]+w;
}
void bagall(int c,int w)//完全揹包
{
    for(v=c;v<=V;v++)
        if(dp[v]<dp[v-c]+w)
            dp[v]=dp[v-c]+w;
}
void mutibag(int c,int w,int p)//多重揹包
{
    if(c*p>=V)
        bagall(c,w);
    else
    {
        int k=1;
        while(k<p)
        {
            bag01(c*k,w*k);
            p=p-k;
            k=k+k;
        }
        bag01(c*p,w*p);
    }
}
int main()
{
    int n[8];
    int i,sum,p=0;
        while(scanf("%d%d%d%d%d%d",&n[1],&n[2],&n[3],&n[4],&n[5],&n[6]),n[1]+n[2]+n[3]+n[4]+n[5]+n[6])
    { 
        sum=n[1]+n[2]*2+n[3]*3+n[4]*4+n[5]*5+n[6]*6;  //sum为奇数个,那么肯定不能平分 
        if(sum%2)
        { 
            printf("Collection #%d:\nCan't be divided.\n\n",++p); 
            continue; 
        } 
        V=sum/2;
        for(i=0;i<=V;i++)
            dp[i]=0;
        for(i=1;i<=6;i++)
            mutibag(i,i,n[i]);
        if(dp[V]==V)
                printf("Collection #%d:\nCan be divided.\n\n",++p); 
        else 
            printf("Collection #%d:\nCan't be divided.\n\n",++p); 
    } 
    return 0;
}

代码我就不罗嗦了,只要了解揹包问题的朋友应该很容易看懂,不懂的童鞋可以看看某大神经典的 揹包九讲。

点赞