【动态规划】背包问题

背包问题

一.0/1背包

题目描述
     一个旅行者有一个最多能用 m 公斤的背包,现在有 n 件物品,它们的重量分别是 W1 ,W2 ,... , Wn ,它们的价值分别为 C1,C2 ,... ,Cn 。若每种物品只有一件求旅行者能获得最大总价值。

输入格式
第 1 行:两个整数,M(背包容量,M≤200)和 N(物品数量,N≤30)。
第 2..N+1 行:每行二个整数 Wi,Ci,表示每个物品的重量和价值。

输出格式
仅一行,一个数,表示最大总价值。

样例数据 1
输入

10 4 
2 1 
3 3 
4 5 
7 9
输出

12


解析:

      令F[i][j]表示从前 i 个物品中选出了总体积为 j 的物品放入背包,物品的最大价值和。

      于是就有F[i][j] = max(F[i - 1][ j] , F[i - 1][j - Vi] + Wi)。其中F[i - 1][ j]表示不选第 i 个物品,F[i - 1][j - Vi] + Wi表示选第 i 

个物品,于是0/1背包解法也就很明确了。

   memset(f,0xcf,sizeof(f)); //-INF
   f[0][0] = 0;
   for(int i = 1;i <= n;i++)
   {
   	 for(int j = 0;j <= m;j++) f[i][j]=f[i-1][j];
   	 for(int j = v[i];j <=m;j++) f[i][j] = max(f[i - 1][j - v[i]] + w[i],f[i][j]);;
   }

通过状态转移方程,我们发现,每一阶段 i 的状态至于 i - 1 的状态有关,于是可以用滚动数组降维。

   int f[2][Max];
   memset(f,0xcf,sizeof(f));   //-INF
   f[0][0] = 0;
   for(int i = 1;i <= n;i++)
   {
   	 for(int j = 0;j <= m;j++) f[i & 1][j]=f[(i-1) & 1][j];
   	 for(int j = v[i];j <=m;j++) f[i & 1][j] = max(f[(i - 1) & 1][j - v[i]] + w[i],f[i & 1][j]);;
   }

接着我们发现,每个阶段开始执行时,执行了从 i - 1  到  i 的拷贝操作,于是我们可以直接省略掉第一维。

   int f[Max];
   memset(f,0xcf,sizeof(f));
   f[0]=0;
   for(int i=1;i<=n;i++)
     for(int j=m;j>=w[i];j--)
       f[j]=max(f[j],f[j-w[i]]+c[i]); 

注意第二层循环的 j 是倒序循环的,这是为了保证每个物品是唯一且只能放入背包一次。


二.完全背包

题目描述

      设有 n 种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为 M ,今从 n 种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于 M ,而价值的和为最大。


输入格式
第 1 行:两个整数,M(背包容量,M<=200)和 N(物品数量,N<=200)。

第 2..N+1 行:每行二个整数 Wi,Ci,表示每个物品的重量和价值。


输出格式

仅一行,一个数,表示最大总价值。


样例数据 1
输入
12 4 
2 1 
3 3 
4 5 

7 9


输出

15


解析:

      与0/1 背包同样的思想,只需注意第二层循环的 j 是正序循环的,这就对应每种物品可以使用无限次

   int f[Max];
   memset(f,0xcf,sizeof(f));
   f[0]=0;
   for(int i=1;i<=n;i++)
     for(int j=w[i];j<=m;j++)
       f[j]=max(f[j],f[j-w[i]]+c[i]); 

三.多重背包

题目描述

      设有 n 种物品,每种物品有一个重量及一个价值。每种物品的数量为c[i],同时有一个背包,最大载重量为 M ,今从 n 种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于 M ,而价值的和为最大。


解析:

      三种方法:直接拆分,二进制拆分与单调队列优化。有兴趣的读者可自行查阅相关资料。


二进制拆分(HUD2191)

#include <bits/stdc++.h>
using namespace std;

const int Max=1011;
int n,m,ans,t,tot;
int w[105],c[105],num[105],val[Max],size[Max];
int f[Max];

inline int get_int()
{
   int x=0,f=1;
   char c;
   for(c=getchar();(!isdigit(c))&&(c!='-');c=getchar());
   if(c=='-') {f=-1;c=getchar();}
   for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
   return x*f;
}

int main()
{
   t=get_int();
   while(t--)
   {
        tot=ans=0;
        memset(f,-1,sizeof(f));
     n=get_int();
     m=get_int();
     for(int i=1;i<=m;i++) w[i]=get_int(),c[i]=get_int(),num[i]=get_int();

     for(register int i=1;i<=m;i++)
     {
       for(register int j=1;j<=num[i];j<<=1)
       {
         size[++tot]=j*w[i];
         val[tot]=j*c[i];
         num[i]-=j;
       }
       if(num[i] > 0)
       {
            size[++tot]=num[i]*w[i];
            val[tot]=num[i]*c[i];
       }
     }

     f[0]=0;
     for(register int i=1;i<=tot;i++)
       for(register int j=n;j>=size[i];j--)
         f[j]=max(f[j],f[j-size[i]]+val[i]);

     for(int i=0;i<=n;i++) ans=max(ans,f[i]);
     cout<<ans<<"\n";
   }

   return 0;
}

四.分组背包

题目描述

       设有 n 组物品,每组有ci个物品,第i组的第j个物品的中重量为Vi,j,价值为Wi,j,同时有一个背包,最大载重量为 M ,今从 物品中选取若干件(同一种物品可以多次选取),使得每组至多选一个物品并且重量的和小于等于 M ,价值的和为最大。


解析:

       思路相似,直接看代码吧

   int f[Max];
   memset(f,0xcf,sizeof(f));
   f[0]=0;
   for(int i=1;i<=n;i++)
     for(int j=m;j>=0;j--)
       for(int k=1;k<=c[i];k++)
         if(j >= w[i][j]) f[j]=max(f[j],f[j-w[i][k]]+c[i][k]); 

猜你喜欢

转载自blog.csdn.net/m0_38083668/article/details/81054166