背包DP

下面介绍一下背包DP主要题型
基础模型(没什么好说的,上模板)

1.01背包
最主要的模板,应用很多,很重要!!!
倒着遍历容积,不会受后选小的f[i][j]影响,已经选过的物品不会再选一遍。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=10020;
int f[N];
int n,m;
int v[N],w[N],s[N];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>v[i]>>w[i];
	}
	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]);
		}
	}
	cout<<f[m];
}

2.完全背包
对于01背包来说,物品可以无限选了。
实现也很简单,只要把01背包的容积倒着遍历改为正着遍历即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=250;
int f[N];
int n,m;
int v[N],w[N];
int main()
{
	cin>>m>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>v[i]>>w[i];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=v[i];j<=m;j++)
		{
			f[j]=max(f[j],f[j-v[i]]+w[i]);
		}
	}
	cout<<"max="<<f[m];
}

3.多重背包
相较于完全背包,物品的个数有限制了。
这是加上二进制优化的最终版本,最后也是将其转化为了01背包。

点击查看代码
#include<iostream>
#include<algorithm> 
#include<cstring> 
#include<cstdio> 
#include<cmath> 
using namespace std;
const int N=8000;
int n,cnt,v[N],f[N],m,C,w[N],s[N];
int main()
{
//	cin>>C;
//	while(C--)
//	{
		cin>>n>>m;
		for(int i=1;i<=n;i++)
		{
			int wi,vi,si;
			cin>>vi>>wi>>si;
			if(si>m/vi)
			{
				si=m/vi;
			}
			for(int j=1;j<=si;j<<=1)
			{
				v[++cnt]=j*vi;
				w[cnt]=j*wi;
				si-=j; 
			}
			if(si>0)
			{
				v[++cnt]=si*vi;
				w[cnt]=si*wi;
			}
		}
		for(int i=1;i<=cnt;i++)
		{
			for(int j=m;j>=v[i];j--)
			{
				f[j]=max(f[j],f[j-v[i]]+w[i]);
			}
		}
		cout<<f[m];
//	}
}

4.二维背包
在容积限制的基础上又加上了质量限制,只需再加一维即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=1000;
int f[N][N];
int n,M,cnt,V;
int v[N],w[N],m[N];
int main()
{
	cin>>V>>M;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>v[i]>>m[i]>>w[i];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=V;j>=v[i];j--)
		{
			for(int k=M;k>=m[i];k--)
			{
				f[j][k]=max(f[j][k],f[j-v[i]][k-m[i]]+w[i]);
			}
		}
	}
	cout<<f[V][M];
}

5.分组背包
把物品分组,每组只能买一个。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=1000;
int f[N];
int n,m,cnt;
int v[N][N],w[N][N],s[N];
int main()
{
	int T,wi,ci,Ti;
	cin>>m>>n>>T;
	for(int i=1;i<=n;i++)
	{
		cin>>wi>>ci>>Ti;
		s[Ti]++;
		w[Ti][s[Ti]]=ci;
		v[Ti][s[Ti]]=wi;
	}
	for(int i=1;i<=T;i++)
	{
		for(int j=m;j>=0;j--)
		{
			for(int k=1;k<=s[i];k++)
			{
				if(j>=v[i][k])
				{
					f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
				}
			}
		}
	}
	cout<<f[m];
}

这些就是基础模板,接下来是例题,你会神奇地发现原来背包还能这么用。

砝码称重

这里我们就可以抽象地把背包看成可称的重量,砝码的重量既是价值,也是容积。
最后遍历一遍有数的背包,就表示可以称出该重量。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=10020;
int f[N],w[10]={0,1,2,3,5,10,20};
int v[N],flag[N],cnt,n,m;
int main()
{
	for(int i=1;i<=6;i++)
	{
		int x;
		cin>>x;
		for(int j=1;j<=x;j++)
		{
			n++;
			v[n]=w[i];
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1000;j>=v[i];j--)
		{
			f[j]=max(f[j],f[j-v[i]]+v[i]);
			flag[f[j]]=1;
		}
	}
	for(int i=1;i<=1000;i++)
	{
		if(flag[i]==1)
		{
			cnt++;
		}
	}
	cout<<"Total="<<cnt;
}

新年趣事之打牌

这里主要是看一下背包找构成数的方法。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=10020;
int f[N],w[N],vis;
int v[N],flag[N],s[N],cnt,n,m;
int main()
{
	cin>>m>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>v[i];
		w[i]=v[i];
	}
	f[0]=1; 
	for(int i=n;i>=1;i--)
	{
		for(int j=m;j>=v[i];j--)
		{
			if(f[j-v[i]]==1)
			{
				if(j==m)
				vis++;
				if(f[j]==0)
				{
					f[j]=1;
					s[j]=v[i];//追踪
				}
			}
		}
	}
	if(vis>1)
	{
		cout<<-1;
		return 0;
	}
	if(f[m]==0)
	{
		cout<<0;
		return 0;
	}
	int t=m;
	while(t)//标记
	{
		flag[s[t]]=1;
		t-=s[t];
	} 
	for(int i=1;i<=n;i++)
	{
		if(flag[v[i]]==0)//遍历
		{
			cout<<i<<" ";
		}
	}
}

背包拓展

1.有依赖的背包
相比于普通的01背包,这里的背包有了从属关系,买有些物品的前提是先买别的。
看一道例题

这是代码的主体部分

点击查看代码
cin>>sum;//买背包所需的价值 
			cin>>num;//可买物品的数量 
			for(int j=1;j<=num;j++)
			{
				cin>>v[j];
				cin>>w[j];
			}
			for(int j=0;j<sum;j++)
			{
				f[i][j]=-inf;//求最大,赋极小 
			}
			for(int j=sum;j<=m;j++)
			{
				f[i][j]=f[i-1][j-sum];
				//把上一个背包的值转移过来 
			}
			for(int j=1;j<=num;j++)
			{
				for(int k=m;k>=w[j];k--)
				{
					f[i][k]=max(f[i][k],f[i][k-v[j]]+w[j]);
				}
			}//正常求在该背包下的01背包 
			for(int j=0;j<=m;j++)
			{
				f[i][j]=max(f[i-1][j],f[i][j]);
			}//比较取最大值 

2.树上背包问题
有依赖的背包进阶版,详情可见我的树型DP。

3.求第k优的解

点击查看代码
#include<iostream>
#include<algorithm> 
#include<cstring> 
#include<cstdio> 
#include<cmath> 
using namespace std;
const int N=2500;
int n,m,k;
int f[N][N],v[N],w[N],a[N],b[N];
int cnt,num;
//f[i][j]表示在i的容积下的第j优解 
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		memset(f,0,sizeof(f));
		memset(w,0,sizeof(w));
		cnt=0;
		num=0;
		cin>>n>>m>>k;
		for(int i=1;i<=n;i++)
		{
			cin>>w[i];
		}
		for(int i=1;i<=n;i++)
		{
			cin>>v[i];
		}
		int i,j,l;
		for(i=1;i<=n;i++)
		{
			for(j=m;j>=v[i];j--)
			{
				for(l=1;l<=k;l++)
				{
					a[l]=f[j-v[i]][l]+w[i];
					b[l]=f[j][l];
				}
				a[l]=b[l]=-1;//防止越界 
				int x,y,z;
				x=y=z=1;
				while(z<=k&&(a[x]!=-1||b[y]!=-1))
				{
					if(a[x]>b[y])
					{
						f[j][z]=a[x];
						x++;
					}
					else
					{
						f[j][z]=b[y];
						y++;
					}
					//排序取值 
					if(f[j][z]!=f[j][z-1])
					//如果结果相同,舍去 
					{
						z++;
					}
				}
			}
		}
		cout<<f[m][k]<<endl;
	}
	
}

总结
背包就总结到这里了,拜拜。

posted @ 2024-02-17 15:07  zhengchenxi  阅读(41)  评论(0)    收藏  举报