CCF 202209-2 何以包邮? (01背包动态规划练习)

一、先温习一下01背包问题

有N件物品和一个容量为V的背包。第i件物品的体积是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。

条件汇总
--------
背包限制容量:Z
此时背包容量:C
物品:1        ,   i        ...    n        //代表编号
重量:wight[1] ,   wight[i] ...    wight[n]
价值:value[1] ,   value[i] ...    value[n]

目标:在wight总和小于Z的情况下,使得value的总和最大

记录已计算好的结果用数组dp表示
dp[i][j]的含义是:从下标为[0至i]的物品⾥任意取,背包容量为j时,价值总和最⼤是多少(一个数字)。

逻辑依据

从这个题目中可以看出,01背包的特点就是:每种物品仅有一件,可以选择放或不放。

  • 确定最优子结构(不用重复的东西确定是没问题的)
  • 状态转移方程(更好的结果就更新)

01背包问题的状态转移方程:

dp[i][wight] = max{

dp[i-1][C]   ,

dp[i-1][C-wight[i]]+value[i]

}

dp[i-1][C]代表的就是不将这件物品放入背包后的总价值

dp[i-1][C-wight[i]]+value[i]则是代表将第i件放入背包之后的总价值

然后就是遍历执行比对,以数组f存储空间换取执行时间速度避免重复。

for (i = 1; i <= n; i++)
    for (j = 1; j <= Z; j--)
        dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - wight[i]] + value[i]);

执行完了一个如同九九乘法表的存储最优配置的数组就生成了,答案靠查表(让C==Z时的那一列的最后一行的值)就行了~

详细步骤

样例是:

5种物品,最大重量5

物品(价值,重量)依次为:【(1,1);(3,2);(5,6);(4,5);(6,4)】

 

方便研究过程的代码

#include <iostream>
#include <cstring>
#include <algorithm>//min、max函数

using namespace std;

const int N = 6;//0~5
int dp[N][N];

void paintDP()
{    
        cout<< "===============" << endl;
    for (int i = 0; i < N; i ++)
    {
        for (int j = 0; j < N; j++)
        {
            cout << dp[i][j] << "\t";
        }
        cout << endl;
    }
        cout<< "===============" << endl;
        
    system("pause");
}

/*
输入样例
5 5
1 3 5 4 6
1 2 6 5 4
*/

int main()
{
    
    system("chcp 65001");//解决utf8中文乱码,另外936代表GBK;

    dp[N][N] = {0};//表格初始化
    int value[N] = {0};
    int weight[N] = {0};

    int n,Z;
    cin >> n >> Z;
    if(n>=N){cout << "行计数物类超限";system("pause"); return 0;}
    if(Z>=N){cout << "列计数重量超限";system("pause"); return 0;}

//商品编号从1开始

    //每个商品的价格
    for (int i = 1; i <= n; i ++)
    {
        cin >> value[i];
    }
    
    //每个商品的重量
    for (int i = 1; i <= n; i ++)
    {
        cin >> weight[i];
    }

 paintDP();

    //生成动态规划表
    for (int i = 1; i <= n; i ++)
    {
            cout << "开始考虑第" << i << "种物品" << endl ;
        //遍历
        for (int j = 1; j <= Z; j++)//矩阵的列是重量,行是物品
        {      

            //上一层的最大值,和上一层减去当前物品重量的【最大值】加上当前物品价值的最大值

            if(j>=weight[i])//否则数组下标就又可能访问上上层失控了!
            {                    
                dp[i][j] = max(
                    //不要i这个物品
                    dp[i - 1][j],
                    //要i这个物品的价值(那个减号是找之前的,没这个重量时的最大值)
                    dp[i - 1][j - weight[i]] + value[i]
                    );
            
                //vscode 里,监视二维数组dp这么写  *(int(*)[6])dp@6             
            }
            else dp[i][j]=dp[i - 1][j];
        }

        paintDP();
    }

    system("pause");
    return 0;
}

二、动态规划策略

在搜索中会重复计算一些结点,所以,如果我们把搜索过程中计算过的结点的值记录下来(前提是最优子结构)以保证不重复计算的话,速度就会提高很多。属于是“以内存空间换处理时间”了。

三、题解

这个题倒着推满足条件时的最大价值时多少,在推的过程中记录,就可以省去生成表格后再找的麻烦。

#include <iostream>
#include <cstring>
#include <algorithm>//min函数

using namespace std;

const int N = 35, M = 3e5 + 10;

int f[N][M];
int a[N];

int main()
{
    int n, x;
    cin >> n >> x;
    int sum = 0;
    for (int i = 1; i <= n; i ++)
    {
        cin >> a[i];
        sum += a[i];//累加总价值
    }
    
    for (int i = 1; i <= n; i ++) f[i][sum] = sum;//最后一列,价值给满
    for (int j = x; j <= sum; j ++) f[0][j] = sum;//第一行,价值给满

    //逆着求动态规划表
    
    int res = 3000010;
    for (int i = 1; i <= n; i ++)
    {
        for (int j = sum; j >= x; j --)//遍历试着往下减
        {
            f[i][j] = f[i - 1][j];
            if (j + a[i] <= sum)//价值更小的话,就更新
             f[i][j] = min(f[i][j], f[i - 1][j + a[i]] - a[i]);

            if (f[i][j] >= x) res = min(res, f[i][j]);//小于x包邮的情况下,找到更小的花费(因为达到后就不再更新res了)
        }
    }
    cout << res;

    system("pause");
    return 0;
}

代码参考:202209-2 CCF 何以包邮? (01背包解法(两种解法) + 详细思路)_一只可爱的小猴子的博客-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

超自然祈祷

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

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

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

打赏作者

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

抵扣说明:

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

余额充值