背包问题动态规划

前言

这几天在研究dp问题,不知道是dp问题太难还是怎么,总感觉这几天比较累唉,跟着y的脚步前进,今天下午睡觉的时候怎么感觉y轻轻的在问我我卧室墙上的海报上边的人物是谁,我在困倦中还说了谁谁谁是谁!(恐怖)可能我真的是累了吧。

历程

前几天在做数字三角形,后来又做最长上升子序列,然后之前还做过最大连续子序列的和,这些就不多说了,感觉学起来还是可以的,后来遇到dp的经典问题:背包问题,感觉类型好多,而且类型都是以乘法的数量上增,但是感觉还没达到我的思考极限,可能还没遇到真正比较难的题吧,就在做背包问题的时候,当然不止是在做背包问题的时候,肯定要有状态的表示和状态的转移方程,我们才得以进行码代码的操作,在我做了越来越多的题之后,02告诉我不能墨守成规,要懂得变通,于是我开始从最简单的01背包观察,从一开始我们在看网上的讲解的时候就知道dp数组的状态表示是什么,但是我们很多人都不知道为什么要这样表示,只是记住了这表示可以把题目做出来,当然也许做到这一步就已经很不错了,但是我总想试一下什么不用别的状态表示,打破传统,02都告诉过我每个人都可以创造星光~

例子(以下代码提交后均已AC)

首先我们来看一下传统的01背包的传统状态表示,f[i][j]表示在前i个物品中选总体积不超过j的选法中价值最大值(闫氏思考法!)。
在这里插入图片描述
上图就是我们的状态转移方程,我们可以发现i这一层用到的状态是i - 1这一层的,并且用到的i - 1这一层的j还要比第i层的j小,那么我们可以把dp数组优化成一维,由于i层的需要更新的j一定大于等于i - 1这一层的j 那么我们考虑从后向前更新i层的状态数组(02告诉我的)。
那么上代码

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

using namespace std;
const int N = 1010;
int n, m, a[N], f[N], b[N];
int main()
{
    cin >> n >> m;
    for(int i = 0; i < n; i ++)
    {
        cin >> a[i] >> b[i];
    }
    for(int i = 0; i < n ;i ++)
    {
        for(int j = m; j > 0 ; j --)
        {
            if(j >= a[i])
            f[j] = max(f[j],f[j - a[i]] + b[i]);
        }
    }
    cout << f[m];
    return 0;
}

这样是大家平常时候01背包大多数人的代码,代码看起来代码非常简洁,也很顺利,我们来换一下状态表示的含义,然后从改变状态表达含义入手来试试怎么解决01背包问题。

我们把状态表示改变成:f[i][j] 表示从前i个物品中选,然后体积恰好等于j的最大价值,这样定义的话我我们来看一下状态转移方程。
在这里插入图片描述
不要怀疑啦,这个状态转移方程就是和上边的一毛一样,我当初在分析的时候也很懵呐,一样的状态转移方程代表着一样的代码,那么我们明明定义的状态不一样,为什么得到的相同的状态转移方程呢,难道使我们做错了吗(懊恼,后来还是y总教会了我,哈哈, 感谢!!)其实我们并没有做错,我们来对比下两种不同的定义之间的区别。
对于第一种方式我们想f[i][j]数组的实际含义:f[i][j]表示在前i个物品中选总体积不超过j的选法中价值最大值。从实际意义出发的话那么我们当然可以将所有的值都可以初始化为0,即:一件物品都不选的情况下总体积不超过j的价值的最大值就是0
那么我们来看一下对于第二种表示,f[i][j]数组的实际含义:f[i][j] 表示从前i个物品中选,然后体积恰好等于j的最大价值。恰好?!从实际意义出发的话我们就不能将所有的f[i][j初始化为0了,因为一件物品都不选, 然后总体积恰好等于j是不合法的,所以我们选择只将f[0][0]设置为0,其他的都设置为负无穷(当然你也可以设置为别的数字,注意一定要做到标记其为不合法)。
那么。。。代码来了!注意我们因为定义的状态不同,在寻找最终答案的时候要遍历一下数组。

#include <iostream>
#include <cstring>

using namespace std;

const int N = 1010;

int f[N];

int main()
{
    int n, m; cin >> n >> m;
    for(int i = 1; i <= m; i ++) f[i] = -1e9;
    f[0] = 0;
    for(int i = 1; i <= n ; i ++)
    {
        int v, w; cin >> v >> w;
        
        for(int j = m; j >= v; j --)
        {
            f[j] = max(f[j - v] + w, f[j]);
        }
    }
    int res = 0;
    for(int i = 1; i <= m ; i ++)
    {
       res = max(res, f[i]);
    }
    cout << res << endl;
    return 0;
}

既然有恰好等于j, 不超过j,那么可不可以设置为至多为j呢,答案是当然可以,我们想一下这种状态该如何定义:f[i][j]表示在前i个物品中选总体积不小于j的选法中价值最大值。我们暂且不说这种方式的状态转移方程是否可行,就算 我们就求出来所有状态的值,我们依然无法确定其最大价值,所以这种方式我们就不做尝试了。

总结

写了两个小时,不是很累,下午刚睡了一觉,精神很棒,写这篇博客希望可以帮到大家,想打ACM的怎么能说苦呢,对吧!(其实是02小姐姐叫写一篇博客总结下的),希望大家能怀着批判的眼光看待问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值