五种简单背包模板

一.引入:

装箱问题(洛谷P1049)
题目描述 有一个箱子容量为V(正整数,0≤V≤20000),同时有n个物品(0<n≤30),每个物品有一个体积(正整数)。
要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。

#include<iostream>
using namespace std;
int v,n,p[35],i,j,f[20005]={0};
int main()
{
	cin>>v;
	cin>>n;
	for(i=1;i<=n;i++)
	{
		cin>>p[i];
	}
	for(i=1;i<=n;i++)
	{
		for(j=v;j>=p[i];j--)
		{
			if(f[j-p[i]]+p[i]>f[j])
			{
				f[j]=f[j-p[i]]+p[i];
			}
		}
	}
	cout<<v-f[v]<<endl;
	return 0;
} 

二、01背包:

采药(洛谷1048)
山洞里有M株不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。在规定的时间T内,采到的草药的总价值最大。

#include<iostream>
using namespace std;
const int N=1010,M=110;
int t,m,f[N],v[M],w[M];
int main()
{
	cin>>t>>m;
	for(int i=1;i<=m;i++)
	{
	    cin>>v[i]>>w[i];//采摘草药的时间,草药价值。
	}
	//模板:就地滚动 
	for(int i=1;i<=m;i++)
	{
		for(int j=t;j>=v[i];j--)//运用上一层状态:从大到小枚举
		{
		    //f[i][j]=f[i-1][j]
		    //f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
			f[j]=max(f[j],f[j-v[i]]+w[i]);
		}
	}
	cout<<f[t]<<endl;
	return 0;
}

三.完全背包:

疯狂的采草药(洛谷P1616)
此题与采药问题有所不同:
1.每种草药可以无限制地疯狂采摘。
2.草药的数目多,采药时间很长。

//每种草药数量没有限制 
//总时间t(1<=t<=10^7) ,草药种类m(1<=m<=10^4) 
//1<=c[i],w[i]<=10^4,m*t<10^7 
#include<iostream>
using namespace std;
const int N=10000010,M=10010;
int t,m,f[N],c[M],w[M];
int main()
{
	cin>>t>>m;//时间t,数目m。
	for(int i=1;i<=m;i++)
	{
	    cin>>c[i]>>w[i];
	}
//模板
//注意:第二个for循环与01背包模板循环方向相反 
	for(int i=1;i<=m;i++)
	{
		for(int j=c[i];j<=t;j++)//运用本层状态:从小到大枚举
		{
			//f[i][j]=f[i-1][j];
			//f[i][j]=max(f[i][j],f[i][j-c[i]]+w[i]);
		    f[j]=max(f[j],f[j-c[i]]+w[i]);
		}
	}
 	cout<<f[t]<<endl;
	return 0;
}

四.多重背包:

样例一:
樱花(洛谷P1833)
有n颗樱花树,每颗树都有美学值Ci,求解在规定时间内看哪几棵樱花树能使美学值最。不同的樱花树能被看的次数有要求,有1到无数次。

//s1现在时间的小时,s2现在时间的分钟,同理e1,e2.z可以用来赏樱花的总时间
//n樱花树的总棵数,f[i]状态转移方程,t[i]看数耗时,c[i]美学值,p[i]次数
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int s1,s2,e1,e2,z,n,f[1005],t[10005],c[10005],p[10005],i,j,k,T[99999],C[99999];
int main()
{
    scanf("%d:%d%d:%d%d",&s1,&s2,&e1,&e2,&n);//计算z
    if(e2<s1)
    {
        e1-=1;
        e2+=60;
    }
    z=(e1-s1)*60+(e2-s2);
    for(i=1;i<=n;i++)//输入数据,并处理可看无数次的树
    {
        scanf("%d%d%d",&t[i],&c[i],&p[i]);
        if(p[i]==0)//0代表无数次
        {
            p[i]=9999;//用一个比较大的值表示无数次
        }
    }
    j=1;
    for(i=1;i<=n;i++)//转化为01背包:将树分解为多颗有规律的树来解决无限次可看树的问题
    {
        k=1;
        while(p[i])
        {
            T[j]=k*t[i];
            C[j]=k*c[i];
            p[i]-=k;
            k=k*2;//系数为1,2,4...2^(k-1),且k是满足p[i]-2^k+1>0的最大整数
            j++;
            if(p[i]<k)
            {
                T[j]=p[i]*t[i];
                C[j]=p[i]*c[i];
                p[i]=0;
                j++;
            }
        }
    }
    n=j;
    for(i=1;i<=n;i++)//01背包模板
    {
        for(j=z;j>=T[i];j--)
        {
            f[j]=max(f[j],f[j-T[i]]+C[i]);
        }
    }
    printf("%d\n",f[z]);
    return 0;
}


样例二:(与样例一有稍微不同)
AcWing 5. 多重背包问题 II
有 N 种物品和一个容量是 V 的背包。第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。

#include<iostream>
using namespace std;
const int N=15000,M=2010;
int n,m,v[N],w[N];
int f[M];
int main()
{
	scanf("%d%d",&n,&m);
	int cnt=0;
	for(int i=1;i<=n;i++)
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		//将数量分解 ->新的物品 
		//1,2,4...2^k,c( c<2^(k+1) ); 
		int k=1;
		while(k<=c)
		{
			cnt++;
			v[cnt]=a*k;
			w[cnt]=b*k;
			c-=k;
			k*=2;
		}
		if(c>0)
		{
			cnt++;
			v[cnt]=a*c;
			w[cnt]=b*c;
		}
	}
	n=cnt;//注意 
	
	//进行01背包操作 
	for(int i=1;i<=n;i++)
	{
		for(int j=m;j>=v[i];j--)
		{
			f[j]=max(f[j],f[j-v[i]]+w[i]);
		}
	}
	printf("%d\n",f[m]);
	return 0;
}

五.二维费用的背包问题:

NASA的食物计划(洛谷P1507)
有两个约束条件:体积V(<400)和质量M(<400)
在这两个约束条件下,输出能达到的食品方案所含卡路里的最大值
解决方法:在01背包的基础上再加一维

//V<=400,M<=400,N<50
//tj存体积,zl存质量,w存所含卡路里
#include<iostream>
using namespace std;
int v,m,n,tj[405],zl[405],w[405],i,j,k,f[405][405]={0};
int main()
{
 cin>>v>>m;
 cin>>n;
 for(i=1;i<=n;i++)
 {
  cin>>tj[i]>>zl[i]>>w[i];
 }
 for(i=1;i<=n;i++)//枚举物品
 {
  for(j=v;j>=tj[i];j--)//枚举体积
  {
   for(k=m;k>=zl[i];k--)//枚举质量
   {
    if(f[j-tj[i]][k-zl[i]]+w[i]>f[j][k])
    {
     f[j][k]=f[j-tj[i]][k-zl[i]]+w[i];
    }
   }
  }
 }
 cout<<f[v][m]<<endl;
 return 0;
}

注:当发现由熟悉的动态规划题目添加限制条件变形得来的题目时,可以尝试在原来的状态中,加一维以满足新的限制条件。

六.分组背包:

通天之分组背包(洛谷P1757)
不同于 01 背包,物品大致可分为 k 组,每组中的物品相互冲突,求最大的利用价值是多少。

//01背包+物品分为k组,每组物品相冲突
//n件物品,总重量为m,zl质量,jz价值,zs所属组数,bh编号,x组号 
#include<iostream>
#include<algorithm>
using namespace std;
int n,m,zl[1005],jz[1005],x,zs[1005]={0},f[1005],bh[1005][1005],i,j,k,t;
int main()
{
 cin>>m>>n;
 for(i=1;i<=n;i++)
 {
  cin>>zl[i]>>jz[i]>>x; 
  t=max(t,x);//记录总组数 
  zs[x]++;//记录该物品在该组的编号,并记录该组的物品总数 
  bh[x][zs[x]]=i;//记录输入该物品的输入顺序 
 }
 for(i=1;i<=t;i++)//遍历所有的组 
 {
  for(j=m;j>=0;j--)//从总总量到0变量,放物品 
  {
   for(k=1;k<=zs[i];k++)//判断该组中的每个物品 
   {
       if(j>=zl[bh[i][k]])//要注意的一步,记得要判断放不放的下 
    f[j]=max(f[j],f[j-zl[bh[i][k]]]+jz[bh[i][k]]);
   }
  }
 }
 cout<<f[m]<<endl;
 return 0;
}

七.扩展:

1.N<=20,V<=10^9
直接搜(2^20)
2.N<=40,V<=10^9
整体二分+暴力枚举

注:如有错误,欢迎指出

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值