Loading

背包问题

Basic Knowledge

01 背包,完全背包,分组背包,多重背包。

Problems

HDU3466 Proud Merchants

Description

\(n\) 个物品,每个物品价值为 \(v_i\)\(m\) 的背包体积。只有当背包剩余体积不少于 \(\max\{p_i,q_i\}\) 时,才能将物品 \(i\) 装入背包,占用 \(p_i\) 的体积。求背包内物品的价值和的最大值。

Solution

此题需要考虑选择物品的顺序。

用贪心的邻项交换思想,考虑物品 \(i\)\(j\),若要都选,所需要剩下的最少体积。若先选 \(i\) 所需要剩下的最小体积更小,则有 \(\max\{q_i,p_i+q_j\}<\max\{q_j,p_j+q_i\}\)

  • \(q_i>p_i+q_j\) 时,\(q_i<p_j+q_i\),显然成立。
  • \(q_j>p_j+q_i\) 时,\(p_i+q_j<q_j\),显然不成立。

以上两种情况我们都无法改变,故不用考虑。所以考虑 \(p_i+q_j<p_j+q_i\)。则 \(p_i-q_i<p_j-q_j\)。所以 \(p_i-q_i\) 较小的需要先选。排序后用普通 01 背包即可。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=505,M=5005,INF=0xc0c0c0c0;
struct Merchant{
	int p,q,v;
	friend bool operator<(const Merchant&A,const Merchant&B){
		return A.p-A.q<B.p-B.q;
	}
}g[N];
int n,m,f[M],ans;
int main(){
	while(scanf("%d%d",&n,&m)!=EOF){
		memset(f,0xc0,sizeof(f));
		ans=0;
		f[0]=0;
		for(int i=1;i<=n;i++){
			scanf("%d%d%d",&g[i].p,&g[i].q,&g[i].v);
		}
		sort(g+1,g+n+1);
		for(int i=1;i<=n;i++){
			for(int j=m+min(g[i].p-g[i].q,0);j>=g[i].p;j--){
				f[j]=max(f[j],f[j-g[i].p]+g[i].v);
			}
		}
		for(int i=0;i<=m;i++)ans=max(ans,f[i]);
		printf("%d\n",ans);
	}
	return 0;
}

Inspiration

此题为什么没看题解时没有做出来?难点在于如何列出微扰式子。都知道要交换邻项,但比较什么?我们应该代入背包问题:体积有限,如何让能够塞入的物品尽量多(价值尽量大已经被背包自身解决)。价值已经不用考虑,故为使背包尽量塞入更多,最好同时塞入两者。又因为背包体积不确定,所以要使得同时塞入两者的最小体积尽量大。

思路:已经解决的不用再考虑,不能在排序时确定的也不用考虑。

HDU3033 I love sneakers!

Description

\(n\) 件物品,\(m\) 的背包体积。\(k\) 组,每个物品都归到一组中,求每组至少选一个能获得的最大价值。(\(n\le 100\)\(m\le 10^4\)\(k\le 10\)

Solution

普通 01 背包不能解决每组都至少选一个的要求。看看其状态转移方程:

\[f_{i,j}=\max\{f_{i-1,j},f_{i-1,j-v_i}+w_i\} \]

也就是说,当不选更优时,它不会去选。

为了强制它在每组中至少选一个,我们按组来划分阶段:

\[f_{i,j}=\max_{a_u=i}\{f_{i-1,j-v_u}+w_u\} \]

这样它就只能选了。但是这样每组只能选一个,因为每次都从上一组转移过来。所以要尝试从本组转移:

\[f_{i,j}=\max_{a_u=i}\{f_{i,j-v_u}+w_u,f_{i-1,j-v_u}+w_u\} \]

思路非常清晰:每组中的物品只能选,不能不选,没有继承。对于每个体积,所有的比较都是在组内进行的,所以保证了这个体积在这一组中至少选一个。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=105,K=15,M=1e4+5,INF=0xc0c0c0c0;
int n,m,k,a[N],v[N],c[N],f[N][M];
vector<int>p[K];
int main(){
	while(scanf("%d%d%d",&n,&m,&k)!=EOF){
		for(int i=1;i<=k;i++){
			p[i].clear();
		}
		for(int i=1;i<=n;i++){
			scanf("%d%d%d",a+i,v+i,c+i);
			p[a[i]].push_back(i);
		}
		memset(f,0xc0,sizeof(f));
		for(int i=0;i<=m;i++)f[0][i]=0;
		for(int i=1;i<=k;i++){
			for(int u:p[i]){
				for(int j=m;j>=v[u];j--){
					f[i][j]=max(f[i][j],f[i][j-v[u]]+c[u]);
					f[i][j]=max(f[i][j],f[i-1][j-v[u]]+c[u]);//其实不需要这样写
				}
			}
		}
		if(f[k][m]>=0)printf("%d\n",f[k][m]);
		else puts("Impossible");
	}
	return 0;
}

Inspiration

没有做出来主要是对背包问题没有完全理解。普通背包问题的状态转移方程分为选与不选两种情况,分组背包将组作为阶段。若能排除不选的情况并按组讨论,这道题就显得简单了。我在讲题时把此题讲得很复杂,其实也是没有完全理解的体现。

HDU5410 CRB and His Birthday

Description

\(n\) 种物品,每种物品无限多。\(m\) 的背包体积。若选择了 \(x\) 件第 \(i\) 种物品,则其价值为 \(a_ix+b_i\),求能够获得的最大价值。

Hint

其实,如果完全理解以上两题(准确地说,只要理解第二题),再结合下面提示,此题就很简单了。

提示: 01 背包 + 完全背包

请思考两分钟。相信你一定想出来了!

posted @ 2022-11-11 21:22  hihihi198  阅读(36)  评论(0)    收藏  举报