第五章 动态规划(4):状态机模型

1 大盗阿福

ACwing 1049

按照之前集合的思路:

集合
f [ i ] f[i] f[i]:表示抢劫前 i i i 家店铺所获得的的收益

集合划分

  • 左边:抢劫第 i i i 家店铺, f [ i − 2 ] + w [ i ] f[i-2]+w[i] f[i2]+w[i](因为选择了第 i i i 家,第 i − 1 i-1 i1 家就不能选择)
  • 右边:不抢劫第 i i i 家店铺, f [ i − 1 ] f[i-1] f[i1]

状态计算
f [ i ] = m a x ( f [ i − 2 ] + w [ i ] , f [ i − 1 ] ) f[i] = max(f[i - 2] + w[i], f[i - 1]) f[i]=max(f[i2]+w[i],f[i1])

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

const int N = 1e5 + 10;

int f[N];

int main() {
   
    int T; scanf("%d", &T);
    while (T--) {
   
        int n; scanf("%d", &n);
        int w; scanf("%d", &w);
        f[1] = w;
        for (int i = 2; i <= n; i++) {
   
            scanf("%d", &w);
            f[i] = max(f[i - 1], f[i - 2] + w);
        }
        printf("%d\n", f[n]);
    }
    return 0;
}

另一种写法

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

const int N = 1e5 + 10;

int num[N];

int main() {
   
    int T; scanf("%d", &T);
    while (T--) {
   
        int n; scanf("%d", &n);
        for (int i = 0; i < n; i++) scanf("%d", num + i);
        int x = num[0], y = max(num[0], num[1]);
        for (int i = 2; i < n; i++) {
   
            int tmp = y;
            y = max(x + num[i], y);
            x = tmp;
        }
        printf("%d\n", y);
    }
    return 0;
}

按照状态机的思路:

将每个 f [ i ] f[i] f[i] 分解成两个状态: f [ i , 0 ] f[i,0] f[i,0] 表示未选择最后一个店铺, f [ i , 1 ] f[i,1] f[i,1] 表示选择最后一个店铺,状态机的每一步都代表原来的一种选法。
在这里插入图片描述

集合

f [ i , 0 ] f[i,0] f[i,0] f [ i , 1 ] f[i,1] f[i,1]
表示所有走了 i i i 步,且当前位于状态 j ( j = 0   o r   1 ) j(j = 0 \,or\, 1) j(j=0or1)的所有走法价值的最大值

集合划分

  • 对于 f [ i , 0 ] f[i,0] f[i,0]

    • 左边:最后一步是 0 → 0 0 → 0 00 f [ i − 1 , 0 ] f[i-1,0] f[i1,0],最后一步没有收益

    • 右边:最后一步是 1 → 0 1 → 0 10 f [ i − 1 , 1 ] f[i-1,1] f[i1,1],最后一步没有收益

  • 对于 f [ i , 1 ] f[i,1] f[i,1],最后一步只能从 f [ i − 1 , 0 ] → f [ i , 1 ] f[i-1,0] → f[i,1] f[i1,0]f[i,1],所以要求 f [ i , 1 ] f[i,1] f[i,1],就需要先求出 f [ i − 1 , 0 ] f[i-1,0] f[i1,0],再加上打劫第 i i i 家店铺的收益,故收益为 f [ i − 1 , 0 ] + w [ i ] f[i-1,0] + w[i] f[i1,0]+w[i]

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

const int N = 1e5 + 10, INF = 0x3f3f3f3f;

int n;
int w[N], f[N][2];

int main() {
   
    int T; scanf("%d", &T);
    while (T--) {
   
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) scanf("%d", w + i);
        f[0][0] = 0, f[0][1] = -INF; // 初始化状态机,状态机入口是状态0
        for (int i = 1; i <= n; i++) {
   
            f[i][0] = max(f[i - 1][0], f[i - 1][1]);
            f[i][1] = f[i - 1][0] + w[i];
        }
        printf("%d\n", max(f[n][0], f[n][1]));
    }
    return 0;
}

1.1 打家劫舍系列

1.1.1 打家劫舍 I

Leetcode 198

解法一:回溯

会超出内存限制,并且数据过大会超时

class Solution {
   
public:
    int rob(vector<int>& nums) {
   
        return dfs(nums.size() - 1, nums);
    }

    int dfs(int i, vector<int> nums) {
   
        if (i < 0) return 0;  // 没有房子可以选择
        return max(dfs(i - 1, nums), dfs(i - 2, nums) + nums[i]);
    }
};

解法二:回溯 + 保存中间结果 = 记忆化搜索

这里的数组的维度取决于函数 d f s dfs dfs 中传入的参数,传一个参数就只有一维,传两个参数就有两维。

class Solution {
   
public:
    int rob(vector<int>& nums) {
   
        int n = nums.size();
        vector<int> memo(n, -1);  // -1 表示没有计算过
        function<int(int)> dfs = [&](int i)->int {
   
            if (i < 0) return 0;
            if (memo[i] != -1) return memo[i];  // 表示已经计算过
            return memo[i] = max(dfs(i - 1), dfs(i - 2) + nums[i]);
        };
        return dfs(n - 1);
    }
};

时间复杂度:状态数目(n) x 单个状态计算时间( O ( 1 ) O(1) O(1))= O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

解法三:递推

注意:

  • 从上往下计算:回溯 / 记忆化搜索
  • 从下往上计算:递推

回溯 / 记忆化搜索 翻译成 递推:

  1. dfs 改成 f 数组
  2. 递归 改成 循环
  3. 递归边界 改成 数组初始值

d f s ( i ) = m a x ( d f s ( i − 1 ) , d f s ( i − 2 ) + n u m s [ i ] ) f [ i ] = m a x ( f [ i − 1 ] , f [ i − 2 ] + n u m s [ i ] ) f [ i + 2 ] = m a x ( f [ i + 1 ] , f [ i ] + n u m s [ i ] ) \begin{aligned} dfs(i) &= max(dfs(i - 1), dfs(i - 2) + nums[i]) \\ f[i] &= max(f[i - 1], f[i - 2] + nums[i]) \\ f[i + 2] &= max(f[i + 1], f[i] + nums[i]) \\ \end{aligned} dfs(i)f[i]f[i+2]=max(dfs(i1),dfs(i2)+nums[i])=max(f[i1],f[i2]+n

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值