动态规划之状态机模型(一)

601 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第18天,点击查看活动详情

状态机

下面来给出状态机的四大概念。

第一个是 State ,状态。一个状态机至少要包含两个状态。例如上面自动门的例子,有 open 和 closed 两个状态。 第二个是 Event ,事件。事件就是执行某个操作的触发条件或者口令。对于自动门,“按下开门按钮”就是一个事件。 第三个是 Action ,动作。事件发生以后要执行动作。例如事件是“按开门按钮”,动作是“开门”。编程的时候,一个 Action一般就对应一个函数。 第四个是 Transition ,变换。也就是从一个状态变化为另一个状态。例如“开门过程”就是一个变换。 2、什么是状态机? 有限状态机(Finite-state machine,FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。 FSM是一种算法思想,简单而言,有限状态机由一组状态、一个初始状态、输入和根据输入及现有状态转换为下一个状态的转换函数组成。 其作用主要是描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。 做需求时,需要了解以下六种元素:起始、终止、现态、次态(目标状态)、动作、条件,我们就可以完成一个状态机图了:

参考博文 状态机

状态机与动态规划之间的联系

我们只是利用状态机来更好的描述动态规划的这个过程。也就是我用状态机来表示动态规划。 我们引入大盗阿福这个题目来说明。我们可以定义f[i]f[i]表示前i加店铺的最大价值。那么我们有 在这里插入图片描述

在这里我们的每一个f[i]f[i]都只有一个状态,表示的是考虑到第i个店铺了的最大价值。而由转态机的定义我们知道至少要有两个状态;那么如果我们这样表示 在这里插入图片描述 这里的第二维的含义和一般的不一样。好比背包问题中f[i][j]f[i][j]表示前i个物品体积为j的最大价值。这里i和j共同表示了一个转态。而本题中是对于i而言,它有两个状态。而这两个状态之间又有联系: 在这里插入图片描述 原本我们动态规划的每一个状态是一个节点,现在,一个节点分为了两个转态来描述。 在这里插入图片描述 我们在进行状态转移的时候,是满足某些条件之后自动转移。

状态机的本质之动态规划描述

状态机本质上是一个有向图(可以有环),每一个点都是一个状态,每一条边都是某种情况。如果满足某种情况就会跳转到节点上

股票买卖系列

股票买卖

题目转送门

题目大意:第一个问题是股票交易最多只能进行一次。

解题思路: 这里我们可以利用贪心来求解,当遍历到第i个股票的时候,我们利用一个数存前i-1个值的最小值,之后有 res = max(res , w[i] - minv) ;之后更新最小值。

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1E5 + 10 , INF = 0x3f3f3f3f;
int n ,w[N];

int main(){
    cin>>n;
    for(int i = 1; i <= n ; ++i)cin>>w[i];
    int res = 0;
    int tp = w[1];
    for(int i = 2; i <= n ; ++i){
        res = max(res , w[i] - tp);
        tp = min(tp , w[i]);
    }
    cout<<res<<endl;
    return 0;
}

这题的动态规划写法可以直接套用股票买卖Ⅳ就可以了,将k改为1即可。

股票买卖Ⅱ

题目转送门

题目大意:这一题交易的次数不限

解题思路:

一、动态规划解法

具体的状态机模型分析如下图: 一共只2有种状态:

  1. 当前处于未持股状态0: 对应可以进行的转换: 0->0 (不买入,继续观望,那么就什么都不发生) 0->1 (买入股票,那么收益就要减去当前市场的股票价格)
  2. 当前处于持股状态1: 对应可以进行的转换: 1->1 (不卖出,继续观望,那么就什么都不发生) 1->0 (卖出股票,那么收益就要加上当前市场的股票价格)

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e5 + 10;
int n ,w[N];
int f[N][2];
int main(){
    cin>>n;
    for(int i = 1; i <= n ; ++i)cin>>w[i];
    memset(f , -0x3f , sizeof f);
    f[0][0] = 0;
    for(int i = 1 ; i <= n ; ++i)
    {
        f[i][0] = max(f[i-1][0] ,f[i-1][1] + w[i]);
        f[i][1] = max(f[i-1][1] ,f[i-1][0] - w[i]);
    }
        
    cout<<max(f[n][1] ,f[n][0])<<endl;
    
    return 0;
}

二、贪心解法

纵然,我们可以用DP搜索出所有的方案数,但是通过观察,我们可以发现本题最优解方案存在一定的性质。

我先贴出测试样例的折线图形式(绿色标出下降,红色标出上升):

考虑一种方案,在每次上升的前一天购入股票,并在上升后的当天卖出的方案

if (w[i] > w[i - 1]) res += w[i] - w[i - 1]; 接下来证明该贪心思路得出的方案即是最优解。

(1)证明贪心解 ≥≥ 最优解: 由于贪心解都是取区间长度为 11 的解,因此假设存在于最优解中的某个区间 [i,j][i,j] 的长度 >1>1 那么会出现一下三种情况:

对应三种情形:最优解选取的区间最终点位于上方、下方、相等。

对于情形一:显然 最优解 << 贪心解 对于情形二:显然 最优解 << 贪心解 对于情形三:毫无疑问,这就是存在于贪心解中的情形,因此 贪心解 == 最优解

得证

(2)证明贪心解 ≤≤ 最优解: 这部分无需证明,因为贪心解即是合法解,所以他的方案必定大于等于最优解

出处

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e5 + 10;
int n ,w[N];

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