• 背包问题


    这里写了作者学过的一些背包问题的解法,希望能为新入门DP的OIer提供便利。

    (LaTeX) 懒得修了,凑合着看吧QwQ~

    2019/11/25:更新了混合背包。
    2020/09/24:更新了多重背包的二进制优化和加二进制优化后的混合背包~

    01背包

    01背包解决的就是有一堆东西,有体积量和价值,要放到一个容量为m的背包里,使得价值之和最大。
    重点: 每个物品只有1个。
    之所以叫01背包,因为没个物品就是取或不取,取是1,不取是0。

    思路:(dp_{i,j})表示前i个物品放到容量为j的背包中的最大价值,则
    (dp_{i,j}=max(dp_{i-1,j},dp_{i-1,j-w_i}+c_i))
    (dp_{i-1,j})表示不取这个东西,那么容量还是(j)(dp_{i-1,j-w_i}+c_i)表示取,那么之前的容量就是(j-w_i)

    优化: 我们发现,我们只需要(dp_{i-1}),而不需要更前面的数据,所以可以换成两个数组的滚动数组,然后,我们发现,只需要i之前的数保留即可,那么可以从后往前赋值,这样只要一个数组就能完成。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    int n,m;
    //n是物品数,m是背包容量 
    int w[505],c[505];
    //wi表示第i个物品的重量,ci表示价值 
    int dp[6005];
    int main()
    {
    	cin>>n>>m;
    	for(int i=1;i<=n;i++) cin>>w[i]>>c[i];
    	for(int i=1;i<=n;i++)//枚举每个物品 
    	 for(int j=m;j>=w[i];j--)//枚举背包容量 
    	 	dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
    	 	//状态转移方程 
    	cout<<dp[m];
    	return 0;
    }
    

    完全背包:

    完全背包,就是一个物品能取无限次,求最大价值。

    我们写01背包时之所以要从后往前,是要避免重复取一个东西,而完全背包就是一个物品能取无限次,所以只要把内层循环改成(w_i)~(m)即可。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    int n,m;
    //n是物品数,m是背包容量 
    int w[505],c[505];
    //wi表示第i个物品的重量,ci表示价值 
    int dp[6005];
    int main()
    {
    	cin>>n>>m;
    	for(int i=1;i<=n;i++) cin>>w[i]>>c[i];
    	for(int i=1;i<=n;i++)//枚举每个物品 
    	 for(int j=w[i];j<=m;j++)//枚举背包容量 
    	 	dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
    	 	//状态转移方程 
    	cout<<dp[m];
    	return 0;
    }
    

    多重背包

    多重背包,第i个物品能取(0)~(s_i)个,求最大价值。

    我们可以把多重背包的一个物品取多次看成多个一样的物品,例如,1号物品有2个,我们可以看成1和1。然后做01背包即可。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    int n,m;
    //n是物品数,m是背包容量 
    int w[505],c[505],s[505];
    //wi表示第i个物品的重量,ci表示价值,si表示数量 
    int dp[6005];
    int main()
    {
    	cin>>n>>m;
    	for(int i=1;i<=n;i++)
    		cin>>w[i]>>c[i]>>s[i];
    	for(int k=1;k<=n;k++)//枚举物品种类 
    	 for(int i=1;i<=s[k];i++)
    	 //枚举这个种类的物品的个数 
    	  for(int j=m;j>=w[k];j--)//枚举背包容量
    	  	dp[j]=max(dp[j],dp[j-w[k]]+c[k]);
    	 	//状态转移方程 
    	cout<<dp[m];
    	return 0;
    }
    

    多重背包优化:二进制优化

    在一些情况中,我们如果把多重背包当01背包来处理,数量太多了,这时我们就需要二进制优化。
    二进制优化就是把多个一样的物品分为1个一组,2个一组,4个一组……n个一组,再把剩下来的搞成一组。
    我们都知道我们可以用2的(1) ~ (n)次幂表示(1) ~ (2^{n+1}-1)的数,所以这样做是可行的。
    例题
    代码:

    #include<cmath>
    #include<cstdio>
    #include<algorithm>
    #define rg register
    using namespace std;
    int T,n,m,mx,cnt,tmp;
    int a[2005],v[2005];
    int t[500005];
    bool dp[500005];
    inline int read()
    {
        int x=0;int f=1;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
        while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
        return x*f;
    }
    int main()
    {
    	n=read(),m=read();
    	for(rg int i=1;i<=n;i++)
    	{
    		int aa,vv;
    		aa=read(),vv=read();
    		for(rg int j=1;vv-j>=0;j*=2)
    		{
    			a[++cnt]=j*aa;
    			vv-=j;
    		}
    		if(vv>0) a[++cnt]=vv*aa;
    	}
    	for(rg int i=1;i<=m;i++)
    	{
    		t[i]=read();
    		if(t[i]>mx) mx=t[i];
    	}
    	dp[0]=true;
    	for(rg int i=1;i<=cnt;i++)
    	{
    		while(dp[tmp]&&tmp<=mx) tmp++;
    		if(tmp>=mx) break;
    		int nd=max(tmp,a[i]);
    		for(rg int j=mx;j>=nd;j--)
    			if(dp[j-a[i]]) dp[j]=true;
    	}
    	for(rg int i=1;i<=m;i++)
    		if(dp[t[i]]) printf("Yes
    ");
    		else printf("No
    ");
    	return 0;
    }
    

    分组背包

    分组背包,就是把东西分成t组,每组最多取1个,最少不取,求最大价值。

    我们可以把每组看成一样物品,只不过它的体积和价值是会变的,我们只要像做01背包那样,最后再循环判断每组里的物品就行了。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    int n,m,t;
    //n是物品数,m是背包容量,t是组数
    int a[15][505];
    //aij表示第i组的第j个物品的编号 
    int w[505],c[505];
    //wi表示第i个物品的重量,ci表示价值 
    int dp[6005];
    int main()
    {
    	cin>>m>>n>>t;
    	for(int i=1;i<=n;i++)
    	{
    		int p;
    		cin>>w[i]>>c[i]>>p;
    		a[p][++a[p][0]]=i;//存储 
    	}
    	for(int i=1;i<=t;i++)//枚举每组物品 
    	 for(int j=m;j>=0;j--)//枚举背包容量 
    	  for(int k=1;k<=a[i][0];k++)
    	  //枚举每组中的每个物品 
    	   if(j>=w[a[i][k]])//判断是否可以放下这个东西 
    	 	dp[j]=max(dp[j],dp[j-w[a[i][k]]]+c[a[i][k]]);
    	 	//状态转移方程 
    	cout<<dp[m];
    	return 0;
    }
    

    当然混合背包中的多重背包也能用二进制优化啦~
    例题
    代码:

    #include<cstdio>
    using namespace std;
    int n,m,t;
    //n是物品数,m是背包容量 
    int w[100005],c[100005];
    //wi表示第i个物品的重量,ci表示价值
    bool p[100005];
    //pi表示状态 
    int dp[1005];
    int tsh,tsm,teh,tem;
    int max(int x,int y){return x>y?x:y;}
    int main()
    {
    	scanf("%d:%d %d:%d %d",&tsh,&tsm,&teh,&tem,&n);
    	tsm+=tsh*60,tem+=teh*60;
    	m=tem-tsm;
    	for(int i=1;i<=n;i++)
    	{
    		int ww,cc,pp;
    		scanf("%d%d%d",&ww,&cc,&pp);
    		if(pp)
    		{
    			for(int j=1;pp-j>=0;j*=2)
    			{
    				t++;
    				w[t]=j*ww;
    				c[t]=j*cc;
    				p[t]=true;
    				pp-=j;
    			}
    			if(pp)
    			{
    				t++;
    				w[t]=pp*ww;
    				c[t]=pp*cc;
    				p[t]=true;
    			}
    		}
    		else
    		{
    			t++;
    			w[t]=ww;
    			c[t]=cc;
    			p[t]=0;
    		}
    	}
    	for(int i=1;i<=t;i++)//枚举每个物品 
    		if(p[i])//如果是01 
    			for(int j=m;j>=w[i];j--)//枚举背包容量 
    			dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
    			//状态转移方程 
    		else
    			for(int j=w[i];j<=m;j++)//枚举背包容量
    				dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
    				//状态转移方程 
    	printf("%d
    ",dp[m]);
    	return 0;
    }
    

    混合背包(01背包+完全背包+多重背包)

    混合背包,就是将多种背包混合在一起,先看题:混合背包

    那么这种情况,我们可以分类讨论。回望之前的01背包和完全背包的代码,我们会发现,只有第二重循环的顺序不同 (废话,就是只改了哪里) 那么我们就可以在第二重循环前判断即可。什么?你不知道哪个是完全背包哪个是01背包?搞个数组标记不就行了嘛。
    然后来看多重背包,那这个更好解决了!只要在输入时预处理,关键部分根本没变。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    int n,m,t;
    //n是物品数,m是背包容量 
    int w[6005],c[6005],p[6005];
    //wi表示第i个物品的重量,ci表示价值
    //pi表示状态 
    int dp[1005];
    int main()
    {
    	cin>>n>>m;
    	for(int i=1;i<=n;i++)
    	{
    		int ww,cc,pp;
    		cin>>ww>>cc>>pp;
    		if(pp)
    		 for(int j=1;j<=pp;j++)
    		 {
    		 	t++;
    		 	w[t]=ww;
    		 	c[t]=cc;
    		 	p[t]=1;
    		 }
    		else
    		{
    			t++;
    			w[t]=ww;
    			c[t]=cc;
    			p[t]=0;
    		}
    	}
    	for(int i=1;i<=t;i++)//枚举每个物品 
    	 if(p[i])//如果是01 
    	  for(int j=m;j>=w[i];j--)//枚举背包容量 
    	 	dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
    	 	//状态转移方程 
    	 else
    	  for(int j=w[i];j<=m;j++)//枚举背包容量
    	     dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
    	 	//状态转移方程 
    	cout<<dp[m];
    	return 0;
    }
    

    持续更新中……只要这个蒟蒻学了新的背包类问题,就会更新。

  • 相关阅读:
    playbook配置不同系统版本的yum源配置
    使用playbook部署lamp
    ansible常用模块
    部署Ansible
    Ansible介绍与安装
    lamp
    mysql主从
    mysql进阶命令
    mysql基础命令
    Dockerfile制作http镜像(以alpine做底层镜像为例)
  • 原文地址:https://www.cnblogs.com/mk-oi/p/13513875.html
Copyright © 2020-2023  润新知