状态机模型
状态机 + 线性dp
AcWing 1049. 大盗阿福
这题比较简单,主要是学习一下状态机的模版(如何定义状态,dp方程如何推导)。
再学一个知识点:线性dp(i由i-1递推过来)可以用滚动数组优化空间复杂度
#include <iostream>
#include <algorithm>
#include <cstring>
const int N = 1e5 + 10;
int f[2][2];//滚动数组优化,线性dp都是可以用滚动数组优化的
int w[N];
int n;
void solve()
{
memset(f, -0x3f, sizeof f);
f[0][0] = 0;
std::cin >> n;
for (int i = 1; i <= n; ++ i)
{
std::cin >> w[i];
}
for (int i = 1; i <= n; ++ i)
{
f[i & 1][0] = std::max(f[(i - 1) & 1][0], f[(i - 1) & 1][1]);
f[i & 1][1] = f[(i - 1) & 1][0] + w[i];
}
std::cout << std::max(f[n & 1][0], f[n & 1][1]) << std::endl;
}
int main()
{
int T;
std::cin >> T;
while (T -- )
{
solve();
}
return 0;
}
AcWing 1057. 股票买卖 IV
限制购买天数
这里也是线性dp,当然可以用滚动数组优化,但是之前大盗阿福写过了,这里就朴素版本了
#include <iostream>
#include <cstring>
const int N = 1e5 + 10, Mk = 110;//N可以开2,即滚动数组优化,但是之前大盗阿福用过了,我这里就朴素版本的了
int f[N][Mk][2];
int w[N];
int n, m;
void solve()
{
memset(f, -0x3f, sizeof f);
f[0][0][0] = 0;
std::cin >> n >> m;
for (int i = 1; i <= n; ++ i)
{
std::cin >> w[i];
}
for (int i = 1; i <= n; ++ i)//枚举天数
{
for (int j = 0; j <= m; ++ j)//枚举交易数
{
//第三维状态,0是卖出,1是买入
/*
错误的递推公式
我们应当先考虑j-1,j从0开始,因此我们需要先判断j不为0,但是如果如果只判断j=0的时候才给f[i][j][0]赋值,
那么假如j = 0的时候f[i][0][0]就没有初始值了,因此我们要提前令 f[i][j][0] = f[i - 1][j][0]; 给它赋值
f[i][j][0] = std::max(f[i - 1][j - 1][1] + w[i], f[i - 1][j][0]);
f[i][j][1] = std::max(f[i - 1][j][0] - w[i], f[i - 1][j][1]);
*/
f[i][j][0] = f[i - 1][j][0];
if (j) f[i][j][0] = std::max(f[i - 1][j - 1][1] + w[i], f[i - 1][j][0]);
f[i][j][1] = std::max(f[i - 1][j][0] - w[i], f[i - 1][j][1]);
}
}
int res = -0x3f3f3f3f;
for (int j = 0; j <= m; ++ j)
{
res = std::max(res, f[n][j][0]);//这里不用枚举i,直接n就行了
}
std::cout << res;
}
int main()
{
solve();
return 0;
}
AcWing 1058. 股票买卖 V
限制了卖出股票的第二天不能买入股票
这题的0状态不是卖出股票的那天,卖出股票的那天是状态2,不是卖出股票且空仓的那天才是状态0
#include <iostream>
#include <cstring>
const int N = 1e5 + 10;
int f[N][3];
int w[N];
int n;
void solve()
{
memset(f, -0x3f, sizeof f);
f[0][0] = 0;
std::cin >> n;
for (int i = 1; i <= n; ++ i)
{
std::cin >> w[i];
}
for (int i = 1; i <= n; ++ i)
{
//0:空仓(冷冻期的后一天) 1:持仓 2:冷冻期(那一天卖出了股票)(其实我们是在状态为2这一天卖出的股票)
f[i][0] = std::max(f[i - 1][2], f[i - 1][0]);
f[i][1] = std::max(f[i - 1][1], f[i - 1][0]- w[i]);//这里不能从f[i - 1][2]转移过来,因为卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
f[i][2] = f[i - 1][1] + w[i];//(其实我们是在状态为2这一天卖出的股票)
}
std::cout << std::max(f[n][0], f[n][2]);
return ;
}
int main()
{
solve();
return 0;
}
AcWing 1053. 修复DNA
这题涉及ac自动机,有点难,后面再来看吧
线性DP+KMP自动机模型
AcWing 1052. 设计密码
这题暂时没ac,但我大概知道啥是状态机了,类似你给他一个数他可以将其对应到一个状态上。
在AcWing 1052. 设计密码中,题意是:n位密码S串,m位的T串,保证我们n位的密码不含m位的T串有多少种设计密码的方案?
我们枚举n位密码,每一位记为i,那么第i位有26种选择方案,第i位每种选择方案可以对应匹配到T串的一个位置,
(比如i匹配到了m的k位置,就是S串的i-k~i匹配T串的1~k,
由此我们发现这不正是kmp吗,他是一种在线算法可以在我们枚举位置i的时候 取出i - 1位置时匹配的长度或者T串中的哪个个位置)
如果一个都匹配不到那么就是T串的0位置,T串我们记录的时候下标从1~m。
因此我们保证我们枚举的每个S串中的位置i不能匹配到T串中的位置m即可,这样就可以保证S串中没有T串。
回到状态机的定义,i的每个字母选择对应T串中的一个位置,本题中合法的位置为T串的0~m - 1(位置m不合法,0是我们自己定义的一个都不匹配的时候的位置)共m个位置,因此我们回忆kmp算法模版,str[j + 1] ?= str[i]
(如果不想等j = ne[j],如果相等 j ++ ),T串中的每个位置j在状态机中能跳到哪个位置完全取决于S串中位置i上的字母,s串上位置i的字母有26种取值方式,因此状态机中m个点有26种不同的跳跃方式,状态机的图中一共有26 * m条边(可能会有重边),我们只要在入口0处跳n
次并且最终不跳到位置m即可。
注意:做本题的时候y总用了偷懒的方法,不偷懒的话就是提前将m个位置的图建好,即预处理g[m][26]这个数组数组的值就是位置j上如果和位置i上的26字母中的k匹配的时候最终可以跳到哪个位置。
代码可以看这个博客课;
我自己wa的那个代码是从i-1转移到i,大部分题解是从i转移到i+1。