状态机模型-dp
状态机介绍
百度百科:
状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作、完成特定操作的控制中心。有限状态机简写为 \(\text{FSM}\)(\(\text{Finite State Machine}\)),主要分为 \(2\) 大类:
-
第一类,若输出只和状态有关而与输入无关,则称为 \(\text{Moore}\) 状态机。
-
第二类,输出不仅和状态有关而且和输入有关系,则称为 \(\text{Mealy}\) 状态机。
简单来说,状态机就是一个数学模型,不是一个实际的机器。它含有几个状态,还有几个函数(或者通俗一点叫“桥梁”),使得这几种状态可以在一定条件下实现相互转化。
是不是和动态规划的状态转移过程很像?
所以有一类型的动态规划题目,它的状态可以在一定的条件下相互转化,这时从状态机的角度来分析会比较好想。
\(\text{OpenJ_NOI - CH0206-8462}\)
给定一个长度为 \(n\) 的序列 \(a_i\),任意相邻的两个 \(a_i,a_{i+1}\) 不能同时选,求选的最大总和。
\(1 \le T \le 50\),\(1 \le n \le 10^5\),\(1 \le a_i \le 1000\)。
从一般 \(\text{dp}\) 思路来想:
设状态 \(f_i\) 表示选了前 \(i\) 个数能获得的最大值,对于第 \(i\) 个数,可以选或者不选,如果选,则第 \(i-1\) 个数不能选,于是可以从 \(f_{i-2}\) 转移过来;如果不选,则从 \(f_{i-1}\) 转移过来:
从状态机的角度想:
根据题意,可以总结出两种状态:选了上一个数,没选上一个数。
- 状态 \(1\) 可以由状态 \(2\) 转移过来,但不能由它自己转移过来(因为不能选两个相邻的数)。
- 状态 \(2\) 既可以由状态 \(1\) 转移过来,也可以由它自己转移过来。
设 \(f_{i,0/1}\) 表示已经处理了前 \(i\) 个数,不选/选第 \(i\) 个数,则状态转移方程为:
每层状态只由上一层状态得到,可以加滚动数组优化空间复杂度为 \(O(1)\)。
#include<iostream>
#include<cstdio>
using namespace std;
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long T, n, dp[2];
int main() {
T = read();
while(T --) {
n = read(), dp[0] = dp[1] = 0;
for(int i = 1; i <= n; i ++) {
long long x = read(), y = dp[0];
dp[0] = max(dp[0], dp[1]);
dp[1] = y + x;
}
cout << max(dp[0], dp[1]) << "\n";
}
return 0;
}
\(\text{acwing-1057}\)
给定一个长度为 \(n\) 的序列,\(a_i\) 表示一个给定股票在第 \(i\) 天的价格。
你最多可以完成 \(k\) 笔交易,求能获取的最大利润。
注意:不能同时参与多笔交易(必须在再次购买前出售掉之前的股票)。
一次买入卖出合为一笔交易。
\(1 \le n \le 10^5\),\(1 \le k \le 100\),\(1 \le a_i \le 10^4\)。
用一般方法就不太容易了,直接用状态机的思路分析。
显然这道题有两种状态:持股 \((1)\) 和不持股 \((0)\)。因此操作可分为四种:
- 买入:\(p = 0 \to p = 1\),持有金额 \(-a_i\)。
- 卖出:\(p = 1 \to p = 0\),持有金额 \(+a_i\)。
- 持仓:\(p = 1 \to p = 1\)。
- 空仓:\(p = 0 \to p = 0\)。
设 \(f_{i,j,p}\) 表示处理完前 \(i\) 天的股票,第 \(i\) 天决策是 \(p\),完成的完整交易数为 \(j\) 的最大利润。
状态转移方程就是这样:
注意:因为一次买入卖出合为一笔交易,因此买入记为一次交易消耗次数,而卖出不消耗次数。
滚动数组可以优化 \(i\) 这一维,空间复杂度为 \(O(k)\),答案为 \(f_{n\%2,j,0}\)。
注意:记得初始化 \(f\) 数组为 \(- \infty\),\(f_{1,0,0}=f_{0,0,0}=0\)。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 100005
#define MAXM 105
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long n, k, a[MAXN], dp[2][MAXM][2], ans;
int main() {
n = read(), k = read();
for(int i = 1; i <= n; i ++) a[i] = read();
memset(dp, -0x3f, sizeof dp);
dp[0][0][0] = dp[1][0][0] = 0;
for(int i = 1; i <= n; i ++) for(int j = 1; j <= min(1ll * i, k); j ++)
dp[i & 1][j][0] = max(dp[i - 1 & 1][j][0], dp[i - 1 & 1][j][1] + a[i]),
dp[i & 1][j][1] = max(dp[i - 1 & 1][j][1], dp[i - 1 & 1][j - 1][0] - a[i]);
for(int i = 1; i <= k; i ++) ans = max(ans, dp[n & 1][i][0]);
cout << ans << "\n";
return 0;
}
\(\text{acwing-1058}\)
给定一个长度为 \(n\) 的序列,\(a_i\) 表示一个给定股票在第 \(i\) 天的价格。
注意:
-
不能同时参与多笔交易(必须在再次购买前出售掉之前的股票)。
-
一次买入卖出合为一笔交易。
-
卖出股票后,无法在第二天买入股票(冷冻期为 \(1\) 天)
求能获取的最大利润。
\(1 \le n \le 10^5\),\(1 \le a_i \le 10^4\)。
显然这道题不是简单的 \(01\) 状态。但可归纳出 \(3\) 种状态:持股 \((0)\)、冷冻期 \((1)\)、未持股 \((2)\)。
操作可分为 \(5\) 种:
- 买入:\(2 \to 0\),持有金额 \(-a_i\)。
- 卖出:\(0 \to 1\),持有金额 \(+a_i\)。
- 持仓:\(0 \to 0\)。
- 空仓:\(2 \to 2\)。
- 冷冻期结束:\(1 \to 2\)。
设 \(f_{i,p}\) 表示处理完前 \(i\) 天的股票,第 \(i\) 天状态是 \(p\) 的最大利润。状态转移方程为:
答案为 \(\max(f_{n,1}, f_{n,2})\),因为最后一天不能持股。
滚动数组可以优化空间复杂度至 \(O(1)\),答案为 \(\max(f_{n\%2,1}, f_{n\%2,2})\)。
注意:记得初始化 \(f\) 数组为 \(- \infty\),\(f_{0,2}=f_{1,2}=0\)。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long n, dp[2][3];
int main() {
n = read();
memset(dp, -0x3f, sizeof dp);
dp[1][2] = dp[0][2] = 0;
for(int i = 1; i <= n; i ++) {
long long x = read();
dp[i & 1][0] = max(dp[i - 1 & 1][0], dp[i - 1 & 1][2] - x);
dp[i & 1][1] = dp[i - 1 & 1][0] + x;
dp[i & 1][2] = max(dp[i - 1 & 1][1], dp[i - 1 & 1][2]);
}
cout << max(dp[n & 1][1], dp[n & 1][2]) << "\n";
return 0;
}
本文来自博客园,作者:So_noSlack,转载请注明原文链接:https://www.cnblogs.com/So-noSlack/p/19219275

浙公网安备 33010602011771号