多重背包题集(HD2844,HD2191,HD1059)

题目描述:

有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

基本算法:

这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取 n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则:f[i][v]=max{f[i-1][v-k*c[i]]+ k*w[i]|0<=k<=n[i]}。复杂度是O(V*∑n[i])。

转化为01背包问题
另一种好想好写的基本方法是转化为01背包求解:把第i种物品换成n[i]件01背包中的物品,则得到了物品数为∑n[i]的01背包问题,直接求解,复杂度仍然是O(V*∑n[i])。但是我们期望将它转化为01背包问题之后能够像完全背包一样降低复杂度。仍然考虑二进制的思想,我们考虑把第i种物品换成若干件物品,使得原问题中第i种物品可取的每种策略——取0..n[i]件——均能等价于取若干件代换以后的物品。另外,取超过n[i]件的策略必不能出现。
方法是:将第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的四件物品。分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示。

但是,有时候单纯只转化为01背包问题,还是会超时,这时我们可以先用完全背包,在使用二进制转化后的01背包。先用完全背包做的前提是:某一种物品的总体积大于容器的体积,那么这时就相当于物品可以无限的取用,因为容器体积满了时,物品总是没有用光的。

看具体题目:

1、HD2844

题意:给出硬币的价值以及该价值硬币的数量还有m,计算在1到m之间的用给定的硬币可以拼凑出的钱的数目。在这个题中,硬币的价值看做物品的体积。

代码:

#include <iostream>
#include<memory.h>
using namespace std;
int n,m,A[101],C[101],vol[101],V[100010];
int main()
{
    int f,count;
    while(cin>>n>>m&&n!=0&&m!=0)
    {
       count=0;
      for(int i=0;i<n;i++)
          cin>>A[i];
      for(int i=0;i<n;i++)
          cin>>C[i];
      memset(V,0,sizeof(V));
      for(int i=0;i<n;i++)
      {
         if(A[i]*C[i]>m)//用完全背包做
         {
           for(int j=A[i];j<=m;j++)
             if(V[j]<V[j-A[i]]+A[i])
                V[j]=V[j-A[i]]+A[i];
         }
         else
        {
            f=1;
            int temp;
          while(C[i]>f)
          {
            temp=A[i]*f;
            for(int j=m;j>=temp;j--)
                if(V[j]<V[j-temp]+temp)
                V[j]=V[j-temp]+temp;
            C[i]-=f;
            f<<=1;
          }
          temp=A[i]*C[i];
          for(int j=m;j>=temp;j--)
            if(V[j]<V[j-temp]+temp)
             V[j]=V[j-temp]+temp;
        }
      }

      for(int i=1;i<=m;i++)
        if(V[i]==i)
            count++;
      cout<<count<<endl;
    }
}

2、HD2191

这个题中,大米价格以及经费的金额看作是容器的体积,而大米的重量看作是物品的价值,根据题意是要求所能得到的大米的最大的重量。

代码:

#include <iostream>
#include<memory.h>
using namespace std;
struct Rice
{
    int p,h;
};
Rice r[501];
int M[20010];
int main()
{
    int C,n,m,a,b,c,f;
    cin>>C;
    for(int i=0;i<C;i++)
    {
        cin>>n>>m;
        memset(M,0,sizeof(M));
        for(int j=0;j<m;j++)
        {
            cin>>a>>b>>c;
            if(a*c>n)//用完全背包
            {
                for(int k=a;k<=n;k++)
                    if(M[k]<M[k-a]+b)
                    M[k]=M[k-a]+b;
            }
            else{
            f=1;
            while(c>f)
            {
                for(int k=n;k>=a*f;k--)
                    if(M[k]<M[k-a*f]+b*f)
                     M[k]=M[k-a*f]+b*f;
                c-=f;
                f<<=1;
            }
           for(int k=n;k>=a*c;k--)
            if(M[k]<M[k-a*c]+b*c)
             M[k]=M[k-a*c]+b*c;
            }
        }
        cout<<M[n]<<endl;
    }
}

3、HD1059

题意:给出1-6每个数字的个数,然后给出的数字能否被分成两部分,使这两部分的和相等。这个题目中数字的价值可看作是背包的体积。

#include <iostream>
#include<memory.h>
using namespace std;
int num[7],V[430000],a=0,vol[100];
int main()
{
   int sum,f;
   bool judge;
   while(true)
   {
        a++;
        judge=false;
        sum=0;
       for(int i=1;i<7;i++)
      {
       cin>>num[i];
       if(num[i]!=0)
       {
           sum+=i*num[i];
           judge=true;
       }
      }
       if(!judge) break;
        cout<<"Collection #"<<a<<":\n";
       if(sum%2){
            cout<<"Can't be divided.\n\n";
            continue;
       }
        sum/=2;
        memset(V,0,sizeof(V));
       for(int i=1;i<7;i++)
       {
           if(i*num[i]>sum)//完全背包
           {
               for(int j=i;j<=sum;j++)
                if(V[j]<V[j-i]+i)
                 V[j]=V[j-i]+i;
           }
           else{
           f=1;
           while(num[i]>f)
           {
              for(int j=sum;j>=i*f;j--)
                if(V[j]<V[j-i*f]+i*f)
                  V[j]=V[j-i*f]+i*f;
              num[i]-=f;
              f<<=1;
           }
           for(int j=sum;j>=num[i]*i;j--)
            if(V[j]<V[j-num[i]*i]+num[i]*i)
              V[j]=V[j-num[i]*i]+num[i]*i;
           }
       }
      if(V[sum]!=sum)
        cout<<"Can't be divided.\n\n";
      else cout<<"Can be divided.\n\n";
   }
}




你可能感兴趣的:(多重背包题集(HD2844,HD2191,HD1059))