【动态规划】背包问题

背包问题

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

单调队列优化:

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

const int Max=2005;
int n,m,ans,t,head,tail;
int w[Max],c[Max],v[Max],f[Max],p[Max];

inline int calc(int i,int u,int k){return f[u + k*v[i]] - k*w[i];}

int main()
{
	scanf("%d",&t);
	while(t--)
	{
	  memset(f,0xcf,sizeof(f));
	  memset(p,0,sizeof(p));
	  f[0]=0;
	  scanf("%d%d",&m,&n);
	  for(int i=1;i<=n;i++)
	  {
	  	scanf("%d%d%d",&v[i],&w[i],&c[i]);
	  	for(int u=0;u<v[i];u++)
	  	{
	  	  tail = 0,head = 1;
		  int maxp = (m-u)/v[i];
		  for(int k=maxp-1;k>=max(0,maxp-c[i]);k--)
		  {
		  	while(head <= tail && calc(i,u,k) >= calc(i,u,p[tail])) tail--;
		  	p[++tail] = k;
		  }
		  for(int q=maxp;q>=0;q--)
		  {
		  	while(head <= tail && p[head] > q - 1) head++;
		  	if(head <= tail) f[u + q*v[i]] = max(calc(i,u,p[head]) + q*w[i],f[u + q*v[i]]);
		  	if(q-c[i]-1 >= 0)
		  	{
		      while(head <= tail && calc(i,u,p[tail]) <= calc(i,u,q-c[i]-1)) tail--;
		      p[++tail] = q-c[i]-1;
		  	}
		  }
	 	}
	  }
	  ans=0;
	  for(int i=1;i<=m;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][k]) f[j]=max(f[j],f[j-w[i][k]]+c[i][k]); 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值