01背包(求具体方案的最小字典序)

23 篇文章 0 订阅

acwing12
在这里插入图片描述
直接上题解,后面写有一般的对于方案没有限制的题解。若不懂如何求出最优解的具体方案,可以先看最下面的一般题解。

字典序最小的方案

状态表示: d p [ i ] [ j ] 表 示 第 i 个 物 品 到 第 n 个 物 品 体 积 为 j 时 的 最 优 解 dp[i][j]表示第i个物品到第n个物品体积为j时的最优解 dp[i][j]inj
因此状态转移方程变为:
d p [ i ] [ j ] = { d p [ i + 1 ] [ j ] j < v [ i ] m a x ( d p [ i + 1 ] [ j ] , d p [ i + 1 ] [ j − v [ i ] ] + w [ i ] ) e l s e dp[i][j] = \begin{cases} dp[i + 1][j] & {j < v[i]}\\max(dp[i + 1][j], dp[i + 1][j - v[i]] + w[i]) & {else}\end{cases} dp[i][j]={dp[i+1][j]max(dp[i+1][j],dp[i+1][jv[i]]+w[i])j<v[i]else
思路:
因为可能有多个最优解,且要求字典序最小的最优解,所以要从第一个物品开始判断,且if的判断条件只能是j >= v[i] && dp[i][j] == dp[i + 1][j - v[i]] + w[i]
若采用dp[i][j] > dp[i + 1][j]则会出错。
例如:
4 5
1 2
2 4
3 4
4 6
2 4 6 6 8
0 4 4 6 8
0 0 4 6 6
0 0 0 6 6
2 3(答案)

int dp[1003][1003];
int f[1003];
int w[N], v[N];

int main()
{
	IOS;
	int n, m; 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 = 1; j <= m; j++)
		{
			dp[i][j] = dp[i + 1][j];
			if (j >= v[i])
				dp[i][j] = max(dp[i][j], dp[i + 1][j - v[i]] + w[i]);
		}
	
	for (int j = m, i = 1; i <= n; i++)
	{
		//注意数组越界
		if (j >= v[i] && dp[i][j] == dp[i + 1][j - v[i]] + w[i])
			f[i] = 1, j -= v[i];
	}

	for (int i = 1; i <= n; i++)
		if (f[i]) cout << i << " ";

	return 0;
}

以下是一般的只需要求出具体方案的题解

思路

一般的求具体方案为正序对n个物品进行状态转移。
状态转移方程:
d p [ i ] [ j ] = { d p [ i − 1 ] [ j ] j < v [ i ] m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) e l s e dp[i][j] = \begin{cases} dp[i - 1][j] & {j < v[i]}\\max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]) & {else}\end{cases} dp[i][j]={dp[i1][j]max(dp[i1][j],dp[i1][jv[i]]+w[i])j<v[i]else

在这里插入图片描述
在这里插入图片描述
代码
if的判断条件可以是 d p [ i ] [ j ] > d p [ i − 1 ] [ j ] dp[i][j] > dp[i - 1][j] dp[i][j]>dp[i1][j]也可以是 j > = v [ i ]    a n d    d p [ i ] [ j ] = = d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] j >= v[i] ~~and~~ dp[i][j] == dp[i - 1][j - v[i]] + w[i] j>=v[i]  and  dp[i][j]==dp[i1][jv[i]]+w[i]
两者的区别:
假如 d p [ n ] [ m ] = d p [ n − 1 ] [ m ] > d p [ n − 2 ] [ m ] dp[n][m] = dp[n - 1][m] > dp[n - 2][m] dp[n][m]=dp[n1][m]>dp[n2][m]。由于是倒序循环的,前者会将 n − 1 n - 1 n1 标记为答案,而后者可能会将n标记为答案。
例如:(条件为前者时跑的样例)
4 5
1 2
2 4
3 4
4 6(样例)
2 2 2 2 2
2 4 6 6 6
2 4 6 6 8
2 4 6 6 8(dp数组)
2 3(方案)
(第二个if条件跑出来的方案则是1,4)

for (int j = m, i = n; i >= 1; i--)
	{
		if (dp[i][j] > dp[i - 1][j])
			f[i] = 1, j -= v[i];
		else f[i] = 0;
	}

注意:使用第二个if条件虽然可以过上面的样例,但是求最优方案最小的字典序仍然时不对的。

一般求具体方案

int dp[1003][1003];
int f[1003];
int w[N], v[N];

int main()
{
	IOS;
	int n, m; 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 = 1; j <= m; j++)
		{
			dp[i][j] = dp[i - 1][j];
			if (j >= v[i])
				dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
		}
	
	for (int j = m, i = n; i >= 1; i--)
	{
		if (j >= v[i] && dp[i][j] == dp[i - 1][j - v[i]] + w[i])
			f[i] = 1, j -= v[i];
		else f[i] = 0;
	}

	for (int i = 1; i <= n; i++)
		if (f[i]) cout << i << " ";

	return 0;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

to cling

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值