多重背包问题的应用

害羞,开始我们先来看看一个经典的算法问题。

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;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值