AcWing 12. 输出01背包字典序最小的最优方案

题目链接:点击这里
在这里插入图片描述
在这里插入图片描述

【输出方案】

一般而言,背包问题是要求一个最优值,如果要求输出这个最优值的方案,可以参照一般动态规划问题输出方案的方法:记录下每个状态的最优值是由状态转移方程的哪一项推出来的,换句话说,记录下它是由哪一个策略推出来的。便可根据这条策略找到上一个状态,从上一个状态接着向前推即可。

还是以 01 背包为例,方程为 F [ i , v ] = m a x { F [ i 1 , v ] , F [ i 1 , v C i ] + W i } F [i, v] = max\left\{F [i − 1, v], F [i − 1, v − C_i] + W_i\right\}

再用一个数组 G [ i , v ] G[i, v]

  1. G [ i , v ] = 0 G[i, v] = 0 表示推出 F [ i , v ] F [i, v] 的值时是采用了方程的前一项(也即 F [ i , v ] = F [ i 1 , v ] F [i, v] = F [i − 1, v]
  2. G [ i , v ] = 1 G[i, v] = 1 表示采用了方程的后一项。

注意这两项分别表示了两种策略:未选第 i i 个物品及选了第 i i 个物品。

那么输出方案的伪代码可以这样写(设最终状态为 F [ N , V ] F [N, V ] ):

i ← N
v ← V
while i > 0
	if G[i, v] = 0
		print 未选第 i 项物品
	else if G[i, v] = 1
		print 选了第 i 项物品
		v ← sv − Ci
	i ← i − 1

另外,采用方程的前一项或后一项也可以在输出方案的过程中根据 F [ i , v ] F [i, v] 的值实时地求出来,也就是说,不必纪录 G G 数组,将上述代码中的 G [ i , v ] = 0 G[i, v] = 0 改成 F [ i , v ] = F [ i 1 , v ] F [i, v] = F [i − 1, v]
G [ i , v ] = 1 G[i, v] = 1 改成 F [ i , v ] = F [ i 1 ] [ v C i ] + W i F [i, v] = F [i − 1][v − C_i] + W_i 也可。

【输出字典序最小的最优方案】

原文链接:https://www.acwing.com/solution/acwing/content/2687/

题目要求输出字典序最小的解,假设存在一个包含第 1 1 个物品的最优解,为了确保字典序最小那么我们必然要选第一个。那么问题就转化成从 2 N 2 \sim N 这些物品中找到最优解。

之前的 f ( i , j ) f(i,j) 记录的都是前 i i 个物品总容量为 j j 的最优解,那么我们现在将 f ( i , j ) f(i,j) 定义为从第 i i 个元素到最后一个元素总容量为 j j 的最优解。接下来考虑状态转移:

f ( i , j ) = m a x ( f ( i + 1 , j ) , f ( i + 1 , j v [ i ] ) + w [ i ] ) f(i,j)=max(f(i+1,j),f(i+1,j−v[i])+w[i])

两种情况,第一种是不选第 i i 个物品,那么最优解等同于从第 i + 1 i+1 个物品到最后一个元素总容量为 j j 的最优解;第二种是选了第 i i 个物品,那么最优解等于当前物品的价值 w [ i ] w[i] 加上从第 i + 1 i+1 个物品到最后一个元素总容量为 j v [ i ] j−v[i] 的最优解。

计算完状态表示后,考虑如何的到最小字典序的解。

首先 f ( 1 , m ) f(1,m) 肯定是最大价值,那么我们便开始考虑能否选取第 1 1 个物品呢。

  • 如果 f ( 1 , m ) = f ( 2 , m v [ 1 ] ) + w [ 1 ] f(1,m)=f(2,m−v[1])+w[1] ,说明选取了第1个物品可以得到最优解。

  • 如果 f ( 1 , m ) = f ( 2 , m ) f(1,m)=f(2,m) ,说明不选取第一个物品才能得到最优解。

  • 如果 f ( 1 , m ) = f ( 2 , m ) = f ( 2 , m v [ 1 ] ) + w [ 1 ] f(1,m)=f(2,m)=f(2,m−v[1])+w[1] ,说明选不选都可以得到最优解,但是为了考虑字典序最小,我们也需要选取该物品。

#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 1010;

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

int main()
{
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; ++i)
		scanf("%d%d", &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(i == n && vol >= v[i])
        {
            printf("%d ", i);
            break;
        }
        
        if(vol <= 0)
            break;
        
        //判断下标是否越界
        if(vol - v[i]>=0 && f[i][vol] == f[i + 1][vol - v[i]] + w[i])
        {
            printf("%d ", i);
            vol -= v[i];        //选了第i个物品,剩余容量就要减小。
        }
    }
    return 0;
}
发布了748 篇原创文章 · 获赞 113 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/qq_42815188/article/details/104417930