状态机模型 dp
状态机介绍
简单来说,状态机就是一个数学模型,不是一个实际的机器。它含有几个状态,还有几个函数(或者通俗一点叫“桥梁”),使得这几种状态可以在一定条件下实现相互转化。
是不是和动态规划的状态转移过程很像?
所以有一类型的动态规划题目,它的状态可以在一定的条件下相互转化,这时从状态机的角度来分析会比较好想。
例题
一. 两种状态
AcWing 1049. 大盗阿福
题目描述
街道上有
第
帮助大盗阿福找出一种偷盗方案,使得获得的总财产最大。
思路
从一般的 dp 思路来思考:
设
综上,状态转移方程为:
从状态机的角度思考:
根据题意,可以总结出两种状态:偷了上一家店铺,没偷上一家店铺。
状态
状态
画个图就是这样的:
设
发现每层状态只会由他的上一层状态得到,所以可以加滚动数组优化。
#include <iostream>
using namespace std;
int T;
int n, a;
int f[2][2];
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d", &n);
f[0][0] = f[0][1] = 0;
for(int i = 1; i <= n; i++) {
scanf("%d", &a);
f[i & 1][0] = max(f[i - 1 & 1][0], f[i - 1 & 1][1]);
f[i & 1][1] = f[i - 1 & 1][0] + a;
}
printf("%d\n", max(f[n & 1][0], f[n & 1][1]));
}
return 0;
}
AcWing 1057. 股票买卖 IV
题目大意:
给定一个长度为
你最多可以完成
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。一次买入卖出合为一笔交易。
思路:
这道题用一般的方法分析就不太容易了,直接用状态机的思路分析。
不难总结出这道题有两种状态:持股
操作可分为四种:
- 买入行为:
- 卖出行为:
- 持仓行为:
- 空仓行为:
画图就是这样的:
令
状态转移方程为:
注意:因为一次买入卖出合为一笔交易,这里将买入算作开始一次交易,所以要消耗性次数,而卖出是一次交易的结束,不消耗次数。
同样可以加滚动数组优化。
#include <cstring>
#include <iostream>
using namespace std;
const int N = 100010, M = 110;
int n, k;
int a[N];
int dp[2][M][2];
int ans;
int main() {
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
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(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]);
ans = max(ans, max(dp[i & 1][j][0], dp[i & 1][j][1]));
}
}
printf("%d", ans);
return 0;
}
P1352 没有上司的舞会
题目大意
给定一棵有
思路
令
但是如果我们这样设计状态会发现无法转移——你不知道当前儿子节点的
根据状态机的思路,本题有两个状态:选当前节点
选当前节点的前提是不能选儿子,
不选当前节点那么两种都能转移,
令
其中
然后就可以快乐地树上递归转移了。
#include <cstring>
#include <iostream>
using namespace std;
const int N = 6010;
typedef long long ll;
int n, ori;
int h[N], e[N], ne[N], idx;
int w[N], f[N][2]; //0 不选,1 选
bool have_father[N];
int root;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dp(int u) {
f[u][1] = w[u];
for(int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
dp(j);
f[u][0] += max(f[j][0], f[j][1]);
f[u][1] += f[j][0];
}
}
int main() {
memset(h, -1, sizeof h);
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &w[i]);
int a, b;
for(int i = 1; i < n; i++) {
scanf("%d%d", &a, &b);
add(b, a);
have_father[a] = true;
}
for(int i = 1; i <= n; i++)
if(!have_father[i]) {
root = i;
break;
}
dp(root);
printf("%d", max(f[root][0], f[root][1]));
return 0;
}
二. 多种状态
AcWing 1058. 股票买卖 V
题目大意:
给定一个长度为
注意:
- 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。一次买入卖出合为一笔交易。
- 卖出股票后,无法在第二天买入股票(冷冻期为
天)。
求能获得的最大利润。
思路:
这道题的状态不再是简单的
但还是一样,可以归纳为
操作可分为五种:
- 买入行为:
- 卖出行为:
- 持仓行为:
- 空仓行为:
- 冷冻期结束:
画图来看就是这样:
设
则状态转移方程为:
#include <cstring>
#include <iostream>
using namespace std;
const int N = 100010;
int n;
int a[N];
int dp[N][3];
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
memset(dp, -0x3f, sizeof dp);
for(int i = 0; i <= n; i++) dp[i][2] = 0;
for(int i = 1; i <= n; i++) {
dp[i][0] = max(dp[i - 1][0], dp[i - 1][2] - a[i]);
dp[i][1] = dp[i - 1][0] + a[i];
dp[i][2] = max(dp[i - 1][2], dp[i - 1][1]);
}
printf("%d", max(dp[n][1], dp[n][2]));
return 0;
}
AcWing 1052. 设计密码
题目大意
你现在需要设计一个密码
的长度是 ; 只包含小写英文字母; 不包含子串 ;
请问共有多少种不同的密码满足要求?
由于答案会非常大,请输出答案模
思路
考虑从前向后构造密码,所以可以将目前构造到第几位作为阶段来 dp。
模板串不能作为字串出现在密码中,考虑 KMP 的过程,可以将目前匹配到了模板串的第几位作为第二维。
设
由题可知,不能匹配到第
接下来考虑状态转移。
首先需要了解一下自动机的概念。
这道题考虑一个状态能更新那些状态要好想很多。
假设我们现在已经构造好了前
然后枚举第
综上所述,状态转移方程为:
#include <cstring>
#include <iostream>
using namespace std;
const int N = 55, mod = 1e9 + 7;
int n, len;
char s[N];
int dp[N][N];
int ne[N];
int main() {
scanf("%d%s", &n, s + 1);
len = strlen(s + 1);
//预处理 ne 数组
for(int i = 2, j = 0; i <= len; i++) {
while(j && s[j + 1] != s[i]) j = ne[j];
if(s[j + 1] == s[i]) j++;
ne[i] = j;
}
dp[0][0] = 1;
for(int i = 0; i < n; i++)
for(int j = 0; j < len; j++)
for(char k = 'a'; k <= 'z'; k++) {
//计算应该更新的状态
int tmp = j;
while(tmp && s[tmp + 1] != k) tmp = ne[tmp];
if(s[tmp + 1] == k) tmp++;
if(tmp < len) dp[i + 1][tmp] = (dp[i + 1][tmp] + dp[i][j]) % mod;
}
int res = 0;
for(int i = 0; i < len; i++) res = (res + dp[n][i]) % mod;
printf("%d", res);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探