算法提高-动态规划-状态机模型

状态机模型

  • 状态机 + 线性dp
    • AcWing 1049. 大盗阿福
    • AcWing 1057. 股票买卖 IV
    • AcWing 1058. 股票买卖 V
    • AcWing 1053. 修复DNA
  • 线性DP+KMP自动机模型
    • AcWing 1052. 设计密码

状态机 + 线性dp

AcWing 1049. 大盗阿福

这题比较简单,主要是学习一下状态机的模版(如何定义状态,dp方程如何推导)。
再学一个知识点:线性dp(i由i-1递推过来)可以用滚动数组优化空间复杂度

#include 
#include 
#include 

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 
#include 

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 
#include 
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。

你可能感兴趣的:(算法,动态规划,状态机,c++,蓝桥杯)