背包问题求方案数、背包问题求具体方案

闫神视频笔记

视频链接

在01背包的基础上要求出最优解的方案数、具体的方案

背包问题求方案数(题目链接

解题思路:

我们可以设置一个与数组f功能类似的数组g,其中f[i]保存的是体积等于i的最优解(这里是等于,之前的博客说的是小于等于,原因后面会说),而g[i]保存的是体积等于i时的最优解的方案数,而f[i]为什么保存的是等于i的最优解的原因是这样更有利于计算方案数(比如最优解的情况下,体积是4,但是背包的容量是6,如果按小于等于来保存结果,最后统计方案数的时候会把体积是5和6的也计算在内,导致重复计算,因为体积是5和6的最优解就是体积为4的最优解)。如果用f[i]保存的是体积等于i的最优解,最后只要把最优解下不同体积的方案数进行求和就行了。

代码:

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 1010,mod = 1000000007,INF = 1000000;
//g[i]保存的是体积为i的最大方案数 
int n,m,v,w,t,s,f[N],g[N];
int main(){
    
    
//	freopen("1.txt","r",stdin);
	cin>>n>>m;
	for(int i=0;i<N;i++) f[i]=-INF;
	g[0]=1;
	for(int i=0;i<n;i++){
    
    
		cin>>v>>w;
		for(int j=m;j>=v;j--){
    
    
			t = max(f[j],f[j-v]+w); 
			s = 0;
			//继承两个体积中价值最大的方案数;如果两个体积的价值相等,则继承两个体积的方案数的和
			if(t==f[j]) s+=g[j];
			if(t==f[j-v]+w) s+=g[j-v];
			if(s>=mod) s%=mod;
			f[j]=t;
			g[j]=s;
		}
	}
	int maxn = -INF,sum=0;
	for(int i=0;i<=m;i++){
    
    
		if(f[i]>maxn){
    
    
			maxn=f[i];
			sum=g[i];
		}else if(maxn==f[i]) sum+=g[i];
	} 
	cout<<sum<<endl; 
	return 0;
}

背包问题求具体方案(题目链接

解题思路:

首先我们要确定如何输出其最优解的具体方案。可以这样判断,如果f[i][j]等于f[i-1][j] 则说明不选第i个物品,如果f[i][j]等于f[i-1][j-v[i]]+w[i] 则说明选第i个物品,通过这样的判断来得出完整的路径。
由于题目要求输出字典序最小的方案,所以我们应该以物体编号从小到大的顺序输出结果。
如果以正常顺序计算最优解,然后再从后往前遍历路径的话,输出的结果就不一定是字典序最小的了

正常顺序的代码:

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 1010;
int n,m,v[N],w[N],f[N][N];
vector<int>V; 
int main(){
    
    
//	freopen("1.txt","r",stdin);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
    
    
		cin>>v[i]>>w[i];
		for(int j=0;j<=m;j++){
    
    
			f[i][j] = f[i-1][j];
			if(j>=v[i]) f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
		}
	}
	int vol = m;
	for(int i=n;i>=1;i--){
    
    
		if(f[i][vol]==f[i-1][vol-v[i]]+w[i]&&vol>=v[i]){
    
    
			vol-=v[i];
			V.push_back(i);
		}
	}
	reverse(V.begin(),V.end());
	for(auto vv:V){
    
    
		cout<<vv<<" ";
	}
	return 0;
}

结果:

输出
237 247 283 286 287 291 299 300 
标准答案
3 4 5 7 10 11 167 237 

原因:

因为遍历路径的时候从后往前,所以是物品编号越大越优先,这样输出的结果会是编号递增情况下字典序最大的结果。

修改:

因此如果我们要字典序最小的结果,就应该反过来操作,计算最优解的时候从后往前,遍历路径的时候从前往后。

正确代码:

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 1010;
int n,m,v[N],w[N],f[N][N]; 
int main(){
    
    
//	freopen("1.txt","r",stdin);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
    
    
		cin>>v[i]>>w[i];
	}
	for(int i=n;i>=1;i--){
    
    
		for(int j=0;j<=m;j++){
    
    
			f[i][j]=f[i+1][j];
			if(j>=v[i]) f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]);
		}
	}
	int vol = m;
	for(int i=1;i<=n;i++){
    
    
		if(f[i][vol]==f[i+1][vol-v[i]]+w[i]&&vol>=v[i]){
    
    
			cout<<i<<" ";
			vol-=v[i];
		}
	}
	return 0;
}

有依赖的背包问题(题目链接

等我学了图再来。。

猜你喜欢

转载自blog.csdn.net/lmmmmmmmmmmmmmmm/article/details/107376567