考前复习——背包问题

01,多重,完全,以及混合背包

对于这种类型的背包问题 我采用的策略是——全都当多重背包来做

那么代码看起来会是这个样子
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define Inf 0x3f3f3f3f
#define N 1005
#define M 505
#define re register

int n,m;
int w[N],c[N];
int cnt;
int f[M];

int main()
{
	/*
	混合背包——'简单'的变种 
	处理一下01 多重 完全的识别即可
	事实上 选一次 多次 无数次 都是算'多次'嘛 
	*/
	cin>>m>>n;
	int a,s,d;
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d%d",&a,&s,&d);//体积 价值 几个
		//把d(物品数量)拆成2进制表示 
		int k=1;
		
		if(d==0)d=m;//这里的d==0代表物品无限的情况(即使该物品体积为1 也最多在背包里装m件)
		
		while(k<=d)
		{
			cnt++;
			w[cnt]=a*k;
			c[cnt]=s*k;
			d-=k;
			k*=2;
		}
		if(d>0)
		{
			cnt++;
			w[cnt]=a*d;
			c[cnt]=s*d;
		}
		
	}
	n=cnt;
	for(int i=1;i<=n;i++)
	{
		for(int j=m;j>=w[i];j--)//在二进制优化之后 便类似于01背包 逆序求解
		{
			f[j]=max(f[j],f[j-w[i]]+c[i]);
		}
	}
	cout<<f[m];
	return 0;
}

分组背包

分组背包其实是从「在所有物品中选择一件」变成了「从当前组中选择一件」,于是就对每一组进行一次 0-1 背包就可以了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define T 15
#define N 1005
#define M 1005
#define re register

int n,m,t;
int w[T][N],c[T][N],cnt[T];
int f[M];

int main()
{
	cin>>m>>n>>t;//容量 物品件数 组数 
	int a,s,d;
	for(re int i=1;i<=n;i++)
	{
		cin>>a>>s>>d;//分组记录 
		w[d][++cnt[d]]=a;
		c[d][cnt[d]]=s;
	}
//	for(int i=1;i<=t;i++)
//		cout<<cnt[i]<<endl;
	for(re int i=1;i<=t;i++)
	{
		for(re int j=m;j>=0;j--)
		{
			for(re int k=1;k<=cnt[i];k++)//第i组的第k个物品 
			{
				if(j>=w[i][k])
					f[j]=max(f[j],f[j-w[i][k]]+c[i][k]);
			}
		}
	}
	cout<<f[m];
	return 0;
}

有依赖的背包问题

金明有 n 元钱,想要买 m 个物品,第 i 件物品的价格为 v_i,重要度为 p_i。有些物品是从属于某个主件物品的附件,要买这个物品,必须购买它的主件。
目标是让所有购买的物品的 v_i * p_i 之和最大。

那么对于以某个主件为主的物品组 只有两种可能:只买该主件,或买主件和其他某些附件。
这两种可能相当于分组背包中的一个组 你只需要在这其中做一次01 得到该物品组的最优 最后取总最优即可

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define N 32005
#define M 65

int n,m;
int m_w[M],m_v[M];
int sum[M];
int a_w[M][3],a_v[M][3];
long long dp[N];

int main()
{
	/*
	有依赖的背包问题——以洛谷p1064为例
	所有的物品由若干主件和依赖于每个主件的一个附件集合组成
	考虑对于每一个主件为首的物品组
	对所有物品组做出最优决策后 只需考虑将所有物品组再归为一个物品组
	再次求解即可
	即 如果直接用分组背包的方法求解 有很多决策是冗余的
	(对于i-1件物品的决策将i的决策重复考虑)
	 
	*/
	cin>>n>>m;//m件物品 背包容量n 
	int w,v,p;
	for(int i=1;i<=m;i++)
	{
		cin>>w>>v>>p;//物品价格 重要度 附属于哪组 
		
		//在该题目中 一个物品的 '体积' 为价格 '价值' 为价格*重要度 
		if(p)
		{
			//不为零则附属于p 
			sum[p]++;
			a_w[p][sum[p]]=w;
			a_v[p][sum[p]]=w*v;
		}
		else
		{
			//为0则主件 
			m_w[i]=w;
			m_v[i]=w*v;
		}
	}
	for(int i=1;i<=m;i++)
		for(int j=n;m_w[i]!=0&&j>=m_w[i];j--)
		{
			//这里是对于一个以主件为首的物品组进行01求解 
			//本题至多有2件附件 故直接分类讨论 
			dp[j]=max(dp[j],dp[j-m_w[i]]+m_v[i]); 
			if(j>=m_w[i]+a_w[i][1])
				dp[j]=max(dp[j],dp[j-m_w[i]-a_w[i][1]]+m_v[i]+a_v[i][1]);
			if(j>=m_w[i]+a_w[i][2])
				dp[j]=max(dp[j],dp[j-m_w[i]-a_w[i][2]]+m_v[i]+a_v[i][2]);
			if(j>=m_w[i]+a_w[i][1]+a_w[i][2])
				dp[j]=max(dp[j],dp[j-m_w[i]-a_w[i][1]-a_w[i][2]]+m_v[i]+a_v[i][1]+a_v[i][2]);
		}
	cout<<dp[n];
	return 0;
}

背包问题的更多变种

  • 输出方案

对于要求输出方案的问题 只需要不断考虑‘当前状态是由哪一个决策引起的’即可

以01背包为例
int w=m;//最终背包被装入的体积 
	for(int i=n;i>0;i--)
	{
		if(f[i][w]==f[i-1][w-c[i]]+v[i])
		{
			//装了第i件物品 
			v-=c[i];
		}
		else if(f[i][w]==f[i-1][w])
		{
			//没装第i件物品 
		}
	}
  • 将背包装至某一容量

注意到在背包问题求解时 已经考虑到了所有状态

因此其它问题的求解可以在此基础上进行

如‘将背包装至某一容量 求有多少方案’这类问题

f[j]=f[j]/*不装方案数*/+f[j-w[i]]/*装了的方案数*/

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define N 5005
#define M 5005
#define ll long long
#define re register

ll m,n;
ll w[N],c[N];
ll f[M];

int main()
{
	cin>>n>>m;
	for(re int i=1;i<=n;i++)
	{
		cin>>w[i];
	}
	f[0]=1;//什么都不装 也是一种方案
	for(re int i=1;i<=n;i++)
	{
		for(re int j=w[i];j<=m;j++)//这里为顺序(完全背包)的做法
		{
			f[j]=f[j]+f[j-w[i]];
		}
	}
	cout<<f[m];//填装至m
	return 0;
}
  • 求最优方案的方案总数

为了对这个问题求解 需要将01背包中的f[][]的定义进行修改

DP 状态 f[i][j] 为在只能放前 i 个物品的情况下,容量为 j 的背包「正好装满」所能达到的最大总价值。

这样修改之后,每一种 DP 状态都可以用一个 g[i,j] 来表示方案数。

f[i,j] 表示只考虑前 i 个物品时背包体积「正好」是 j 时的最大价值。

g[i,j] 表示只考虑前 i 个物品时背包体积「正好」是 j 时的方案数。

转移方程:

如果 f_{i,j} = f_{i-1,j} 且 f_{i,j} ≠ f_{i-1,j-v}+w 说明我们此时不选择把物品放入背包更优,方案数由 g_{i-1,j} 转移过来,

如果 f_{i,j} ≠ f_{i-1,j} 且 f_{i,j} = f_{i-1,j-v}+w 说明我们此时选择把物品放入背包更优,方案数由 g_{i-1,j-v} 转移过来,

如果 f_{i,j} = f_{i-1,j} 且 f_{i,j} = f_{i-1,j-v}+w 说明放入或不放入都能取得最优解,方案数由 g_{i-1,j} 和 g_{i-1,j-v} 转移过来。

初始条件:
memset(f, 0x3f3f, sizeof(f));  // 避免没有装满而进行了转移
f[0] = 0;
g[0] = 1;  // 什么都不装是一种方案

因为背包体积最大值有可能装不满,所以最优解不一定是 f[m]。

最后我们通过找到最优解的价值,把 g[j] 数组里取到最优解的所有方案数相加即可。

点击查看代码
for (int i = 0; i < n; i++) {
  for (int j = V; j >= v[i]; j--) {
    int tmp = max(dp[j], dp[j - v[i]] + w[i]);
    int c = 0;
    if (tmp == dp[j]) c += cnt[j];                       // 如果从dp[j]转移
    if (tmp == dp[j - v[i]] + w[i]) c += cnt[j - v[i]];  // 如果从dp[j-v[i]]转移
    dp[j] = tmp;
    cnt[j] = c;
  }
}
int max = 0;  // 寻找最优解
for (int i = 0; i <= n; i++) {
  max = max(max, dp[i]);
}
int res = 0;
for (int i = 0; i <= V; i++) {
  if (dp[i] == max) {
    res += cnt[i];  // 求和最优解方案数
  }
}

posted @ 2023-06-20 11:40  pig_pig  阅读(15)  评论(0)    收藏  举报