背包问题----分组背包(超详细讲解)

分组背包

1.定义

分组背包,通俗的讲就是,给你N组物品,然后每一组你至多选择一个物品(也可以不选),每个物品都有自己的体积和价值,现在给你一个容里为M的背包,让你用这个背包装物品,使得物品价值总和最大.

2.讲解

其实就类似于01背包,对于一个物品有两种决策选或不选,但是分组背包是在01背包的基础上对物品进行了分组,并且每一组只能最多选择一个物品,所以我们不妨用01背包的思想去思考分组背包.

分析:我们设f[i][j]为当前考虑到了第i组物品,剩余容里为j的背包能装物品的最大价值,那么很容易想到我们需要去枚举第i组物品,考虑选哪一个物品时最优的(或者不选),状态转移方程就是 i f ( j > = v [ i ] [ k ] ) f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − v [ i ] [ j ] ] + w [ i ] [ k ] ) if(j>=v[i][k]) f[i][j] = max(f[i][j],f[i-v[i][j]]+w[i][k]) if(j>=v[i][k])f[i][j]=max(f[i][j],f[iv[i][j]]+w[i][k]),v[i][k]和w[i][k]分别表示第i组物品中第k个物品的体积和价值

代码:

for(int i=1;i<=n;i++)
	 for(int j=0;j<=m;j--)
	  for(int k=1;k<=s[i];k++)//s[i]表示第i组物品的个数
	   if(j>=v[i][k])//剩余的背包容量j大于第i组的第k个物品的体积 
	   {
    
    
	   	  f[i][j] = max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
	   }

这里我们还可以对空间进行优化,我们可以观察到,f[i][…]只会用到f[i-1][…]的值,所以数组的第一维的空间完全可以用滚动数组的方式处理掉,但是如何不影响状态转移呢,我们来看滚掉之后的状态转移方程, f [ j ] = m a x ( f [ j ] , f [ j − v [ i ] [ k ] ] + w [ i ] [ k ] ) f[j] = max(f[j],f[j-v[i][k]]+w[i][k]) f[j]=max(f[j],f[jv[i][k]]+w[i][k]),这里的max里面的 f [ j ] 和 f [ j − v [ i ] [ k ] ] f[j]和f[j-v[i][k]] f[j]f[jv[i][k]]其实是 f [ i − 1 ] [ j ] 和 f [ i − 1 ] [ j − v [ i ] [ k ] ] f[i-1][j]和f[i-1][j-v[i][k]] f[i1][j]f[i1][jv[i][k]],而不是 f [ i ] [ j ] 和 f [ i ] [ j − v [ i ] [ k ] ] f[i][j]和f[i][j-v[i][k]] f[i][j]f[i][jv[i][k]],所以我们需要对体积的遍历做一些修改,从大到小循环,如果还是从小到大循环的话,那么这里的 f [ j ] 和 f [ j − v [ i ] [ k ] ] f[j]和f[j-v[i][k]] f[j]f[jv[i][k]]的含义就有可能是 f [ i ] [ j ] 和 f [ i ] [ j − v [ i ] [ k ] ] f[i][j]和f[i][j-v[i][k]] f[i][j]f[i][jv[i][k]],而不是我们需要的 f [ i − 1 ] [ j ] 和 f [ i − 1 ] [ j − v [ i ] [ k ] ] f[i-1][j]和f[i-1][j-v[i][k]] f[i1][j]f[i1][jv[i][k]],可以模拟一下就明白了,只靠想的话有点抽象.

3.练习题

下面给大家一道经典分组背包的练习题(练手)

题目链接
http://acm.hdu.edu.cn/showproblem.php?pid=1712
AC代码:

#include<bits/stdc++.h>
using namespace std;
int w[105][105];
int f[105]={
    
    0};
int main()
{
    
    
	int n,m;
	while(cin>>n>>m)
	{
    
    
	if(!n&&!m) break;
	memset(f,0,sizeof(f));
	/*w[i][j]表示在i课程上花j天学习所获得的价值*/
	for(int i=1;i<=n;i++)
	 for(int j=1;j<=m;j++)
	 	cin>>w[i][j];
	 
	for(int i=1;i<=n;i++)
	 for(int j=m;j>=0;j--)
	  for(int k=1;k<=m;k++)
	   if(j>=k)//剩余的背包容量j还够学习k天的话 
	   {
    
    
	   	  f[j] = max(f[j],f[j-k]+w[i][k]);
	   }
	   cout<<f[m]<<'\n';
    }
    return 0;
} 

依赖背包

1.定义

什么是依赖背包,顾名思义就是具有依赖属性,这种背包常见于树形结构上面,例如:一棵树有N个节点,每一个节点放有一个物品,这些物品有自己的体积和价值,但是如果你要选择v好节点的物品,那么必须先选择v的父亲节点上的物品(所谓的依赖关系),现在你有容里为M的背包,问你选择物品的最大权值和是多少.

2.讲解

我们这里设dp[u][j]表示以u为根节点,背包剩余容里为j能够选择的物品的最大权值和,那么可想而知dp[u][j]这个值一定是由子节点更新来的,状态转移方程如下,用到了滚动数组优化

/*这里i最小为v[u]因为你要选子节点的话,u这个节点必选,给u留空间*/
for(int i=m;i>=v[u];i--) 
 for(int k=0;k<=i-v[u];i++)
  f[u][i] = max(f[u][i],f[u][i-k]+f[s][k]);//s是u的子节点 

那么很显然答案就是f[root][m],root是我们的根节点

3.练习题

题目链接
https://www.luogu.com.cn/problem/P1273

这道题就是分组背包+依赖背包的结合体型,一道很经典的树形dp

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int Maxn = 3010;
struct Edge
{
    
    
	int v;
	int next;
	int w;
}edge[10000];
int head[Maxn];
int val[Maxn];
int f[Maxn][Maxn];//f[u][j]以u为根节点,选择j个用户赚取的最大价值 
int cnt = 0;
void build(int u,int v,int w)
{
    
    
	edge[++cnt].v = v;
	edge[cnt].w = w;
	edge[cnt].next = head[u];
	head[u] = cnt;
	return ;
}
int n,m;
int dfs(int u)
{
    
    
	/*说明是用户*/
	if(u>(n-m))
	{
    
    
		f[u][1] = val[u];
		return 1;//用户个数 
	}
	int sum = 0,now;
	for(int i=head[u];i!=-1;i=edge[i].next)
	{
    
    
		int v = edge[i].v;
		now = dfs(v);//以v为根节点的子树有多少个用户 
		sum+=now;
		for(int j=sum;j>=0;j--)
		 for(int k=1;k<=now;k++)
		 {
    
    
		 	if(j>=k)
		 	f[u][j] = max(f[u][j],f[u][j-k]+f[v][k]-edge[i].w);//edge[i].w是u到v的花费 
		 }
	}
	return sum;
	
}
int main()
{
    
    
	memset(f,~0x7f,sizeof(f));
 	memset(head,-1,sizeof(head));
 	cin>>n>>m;
	for(int i=1;i<=n;i++) f[i][0] = 0;//选0个用户赚不到钱 
	
	for(int i=1;i<=(n-m);i++)
	{
    
    
		int k;
		cin>>k;
		for(int j=1;j<=k;j++)
		{
    
    
			int A,C;
			cin>>A>>C;
			build(i,A,C);
		}
	}
	
	for(int i=n-m+1;i<=n;i++) cin>>val[i];
	dfs(1);
	for(int i=m;i>=0;i--)
	{
    
    
		if(f[1][i]>=0)
		{
    
    
			cout<<i<<'\n';
			break;
		} 
	}
	return 0;
}

题目连接
https://www.luogu.com.cn/problem/P2014
这是一道基于分组背包的模板题

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int Maxn = 400;
struct Edge
{
    
    
	int v;
	int next;
}edge[Maxn];
int cnt = 0;
int head[Maxn];
int w[Maxn];
int sz[Maxn];
int f[Maxn][Maxn];
void dfs(int u)
{
    
    
	f[u][1] = w[u];
//    f[u][0] = 0;
	sz[u] = 1;
	for(int i=head[u];i!=-1;i=edge[i].next)
	{
    
    
		int v =edge[i].v;
		dfs(v);
		sz[u]+=sz[v];
		for(int j=sz[u];j>=0;j--)
		 for(int k = 0;k<=sz[v];k++)
		 {
    
    
		 	/*j-k>=1,因为选儿子的话必须选父亲要给父亲留位置*/
		 	if(j>k) f[u][j] = max(f[u][j],f[u][j-k]+f[v][k]);
		 }
	}
	return ;
}
void build(int u,int v)
{
    
    
	edge[++cnt].v = v;
	edge[cnt].next = head[u];
	head[u] = cnt;
	return ;
}
int main()
{
    
    
	memset(f,~0x7f,sizeof(f));
	memset(head,-1,sizeof(head));
	
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++) f[i][0] = 0;
	for(int i=1;i<=n;i++)
	{
    
    
		int u;
		cin>>u>>w[i];
		build(u,i);
	}
	w[0] = 0;
	dfs(0);
	cout<<f[0][m+1]<<'\n';
	return 0;
}

猜你喜欢

转载自blog.csdn.net/TheWayForDream/article/details/116567088
今日推荐