背包问题求解具体方案

背包问题求具体方案

分解一下,背包问题求具体方案=先解决背包问题,再求具体放进背包什么物品。

1.01背包问题求最大价值

题目描述:
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据:
4 5
1 2
2 4
3 4
4 6

#include<iostream>
#include<algorithm>
using namespace std;

const int N = 1010;

int f[N][N];   //f[i][j]表示前i个物品,体积不超过j的最大价值
int w[N], v[N];   //w[i]表示第i个物品的价值,v[i]表示第i个物品的体积

int n, m;   //n表示物品个数,m表示背包最大容积

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		cin >> v[i] >> w[i];
	for (int i = 1; i <= n; i++)  //枚举物品
	{
		for (int j = 0; j <= m; j++)   //枚举背包容积
		{
			f[i][j] = f[i - 1][j];   //不选第i个物品
			if (j >= v[i])    //选或不选第i个物品
				f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
		}
	}
	cout << f[n][m];
	return 0;
}

2.求01背包问题最大价值的方案

上面求出了01背包的最大价值,那么在求最大价值的方案之前,先看一个表格,从表格出发来解决问题:
在这里插入图片描述
在这里插入图片描述
发现,f[i][j]由两个状态转移而来,分别是两个绿色的箭头,如果j<v[i],那么就只能从f[i-1][j]转移过来。

在这里插入图片描述
不同颜色的箭头表示不同‘类’,状态方程的转移只能从相同’类‘中转移。那么想要找到最大价值的方案。从最后黄颜色的格子往前推。
通过从前往后推,可以知道,有三种情况;

  1. f[i][j]由f[i-1][j]得到,即没有选第i件物品。
  2. f[i][j]由f[i-1][j-v[i]]+w[i]得到,即选择了第i件物品。
  3. 还有可能f[i-1][j]==f[i-1][j-v[i]]+w[i]。即选择第i件物品和不选择第i件物品的价值一样大

那么就可以通过这几点来写代码。

#include<iostream>
#include<algorithm>
using namespace std;

const int N = 1010;

int f[N][N];   //f[i][j]表示前i个物品,体积不超过j的最大价值
int w[N], v[N];   //w[i]表示第i个物品的价值,v[i]表示第i个物品的体积

int n, m;   //n表示物品个数,m表示背包最大容积

int sum = 0;
void dfs(int i, int j)  //n是物品编号,m是体积
{
	//终止条件
	if (j == 0)
	{
		sum++;  //方案数加1
		cout << endl;
		return;
	}
	
	if (j < v[i]) //不能装下第i件物品,那么只能从上一层同一列转移过来(看图)
	{
		//那么就说明这个路线的方案没有第i件物品!
		dfs(i - 1, j);
	}

	if (j >= v[i])  //如果可以装下第i件物品,那么就看怎么转移过来的。要么f[i-1][j],要么f[i-1][j-v[i]]+w[i]。
	{
		if (f[i - 1][j] == f[i - 1][j - v[i]] + w[i])  //两个状态转移过来都可以
		{
			dfs(i - 1, j);//要么不选i
			//要么选i
			cout << i << " ";
			dfs(i - 1, j - v[i]);  //要么选i
		}

		else
		{
			if (f[i - 1][j] > f[i - 1][j - v[i]] + w[i])  //不选i的方案
			{
				dfs(i - 1, j);
			}
			else  //选i
			{
				cout << i << " ";
				dfs(i - 1, j - v[i]);
			}
		}
	}

		

}
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		cin >> v[i] >> w[i];
	for (int i = 1; i <= n; i++)  //枚举物品
	{
		for (int j = 0; j <= m; j++)   //枚举背包容积
		{
			f[i][j] = f[i - 1][j];   //不选第i个物品
			if (j >= v[i])    //选或不选第i个物品
				f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
		}
	}
	cout << f[n][m] << endl;
	  //从f[n][m]开始
	dfs(4, 5);
	cout << "一共有" << sum << "条路径!\n";
	return 0;
}

以上就是最基本的求背包方案。但是通常不会这么简单
在这里插入图片描述
首先解释一下字典序最小:
就是比较字符串大小!
比如:
123和223,那么223>123.也就是说,从第一个字符开始比较ascil码,如果相同,继续比较下一个,一直到有一个字符不相同,那么ascil码值大的就是字典序大的一方。

如果要满足字典序最小,那么从物品编号1开始到n结束,每一次选择的编号都应该尽可能的小。
下面的图是逆序枚举物品求得的f[i][j]

在这里插入图片描述

还是分为两步,先求背包问题,再求方案

#include<iostream>
#include<algorithm>
using namespace std;

const int N=1010;

int f[N][N];
int w[N],v[N];
int n,m;

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)   //输入体积和价值(n件物品)
        cin>>v[i]>>w[i];
    for(int i=n;i>=1;i--) //逆序,这样最终f[1][m]就是表示的是前n件物品体积为m下的最大价值
    {
        for(int j=0;j<=m;j++)
        {
            f[i][j]=f[i+1][j];  //表示不选第i件物品
            if(j>=v[i])  //当前容量可以装下当前物品
                f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]);
        }
    }
    
    int j=m; 
    //由于f[1][m]是最大价值,但是不知道选取了什么物品,所以要判断选取了什么物品
    //如果第i件物品选了,那么f[i][j]==f[i+1][j-v[i]]+w[i],
    //如果第i件物品没有选,那么f[i][j]==f[i+1][j];
    for(int i=1;i<=n;i++)
    {
        if(j>=v[i]&&f[i][j]==f[i+1][j-v[i]]+w[i])  //能装就要装,保证不跳,越连续字典序越小
        {
             cout<<i<<' ';
             j-=v[i];
        }
        
    }
    return 0;
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值