背包问题求具体方案(AcWing, 背包九讲)

题目描述:

背包问题求具体方案(AcWing, 背包九讲)_第1张图片

 题目链接:

https://www.acwing.com/problem/content/12/

 思路: 这个题有一定的难度,思想很妙,第一次做的话不是很容易想到怎么处理。

比较直观的思路是用dp,f[i][j] 表示使用1~i的物品,且体积小于等于j 的最大价值, g[i][j] 表示到达f[i][j]这个状态的最后一步最优决策。  这样整个dp数组更新完之后,就可以从n往前推,得到整个最优决策序列,但是这种做法没法保证得到的解是“字典序最小的”。

yxc老师说,一般让我们求解“字典序最小的方案”,常用的做法是“贪心”。 如何贪心? 我们先从1号物品考虑,关于1号物品的选取会有3种情况:

情况1: 1号物品一定要选(dp转移式中更优的决策), 那么此时一定要选1号物品

情况2: 1号物品一定不能选(dp转移式中更不优的决策), 那么此时一定不选1号物品

情况3: 1号物品可选可不选(dp转移式中两种决策价值相同), 那么此时一定要选1号物品,因为1号物品是字典序最小的,如果不选,则不可能最优。

在保证1号物品处的决策是最优的前提下(贪心的前提),继续考虑2号物品是否选取,从前往后不断考虑。

这个过程启发我们,dp数组可以从后往前更新。这样在最后就是从1往后推,得到整个最优决策序列,并且能够保证字典序是最小的。

具体来说,f[i][j] 的定义: i~n 这些物品中,体积小于等于j的最大价值

g[i][j] 表示到达f[i][j]这个状态的最后一步最优决策. 

f[i][j] =max(f[i+1][j] , f[i+1][j-v[i]]+w[i]); 若两者相等,则优先选取后者(因为他的字典序更小)

最后从1开始往后推,就可以得到最终答案了。  需要注意一些边界情况。

AC代码:

#include
#include
#include
#include
using namespace std;
const int N=1010;
const int inf=-0x3f3f3f3f;
int f[N][N],g[N][N];
int n,m;
int v[N],w[N];

int main(void){
	
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d%d",&v[i],&w[i]);
	}
	for(int i=0;i<=m;i++) g[n+1][i]=n+1;
	for(int i=n;i>=1;i--){
		for(int j=0;j<=m;j++){
			f[i][j]=f[i+1][j];
			g[i][j]=g[i+1][j];
			if(j>=v[i]&&f[i][j]<=f[i+1][j-v[i]]+w[i]){
				g[i][j]=i;
				f[i][j]=f[i+1][j-v[i]]+w[i];
			}
		}
	}
	int cur=m;
	int now_ind=1;
	while(cur>0 && now_ind<=n){
		int x=g[now_ind][cur];
		if(x>n) break;
		printf("%d ",x);
		cur-=v[x];
		now_ind=x+1;
	}
	return 0;
}

你可能感兴趣的:(算法题,算法,leetcode,数据结构,贪心算法,动态规划)