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

39 阅读2分钟

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

股票买卖Ⅲ

题目转送门

解题思路: 这一题如果用动态规划解的话,只需要将股票买卖Ⅳ中的K该位3即可。

前后缀分解

代码:

#include<iostream>
using namespace std;
const int N = 1e5+10;
int dp1[N],dp2[N];
int a[N];
int main()
{
    int n;
    cin>>n;
    for(int i = 1; i <= n; i ++) cin>>a[i];
    int t = 1e5;
    for(int i = 1; i <= n; i ++)
    {
        t = min(t,a[i]);//记录前面最小的数
        dp1[i] = max(dp1[i-1],a[i] - t);
    }
    t = -1;
    for(int i = n; i >= 1; i --)
    {
        t = max(t,a[i]);//因为是从后面开始记录,那么取后面最大的赚取的利润才最大。
        dp2[i] = max(dp2[i+1],t - a[i]);
    }
    int res = -1;
    for(int i = 1; i <= n; i ++)
        res = max(res,dp1[i] + dp2[i+1]);
    cout<<res;
    return 0;
}

股票买卖Ⅳ

题目转送门

解题思路: 在这里插入图片描述 对于什么时候是j什么时候是j-1的问题,我们需要从状态表示的含义进行出发;我们以这个为例来分析, 对于f[i,j,0]f[i,j,0]我们从有货变成无货的话,那么意义应该是前i-1个物品,我们已经交易完j-1次交易,且目前有第j张股票,我们将其卖去,得的价值是今天股票的价值w[i[w[i[因为今天股票的价格是固定的,我们只需将前边的收益最大化。而这正好是f[i,j,1]f[i,j,1]的含义。

同时对于状态机,状态机的入口全部初始化为0,非入口全初始化为无穷。

代码:

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

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

股票买卖Ⅴ

题目转送门

状态机模型 我们不能遗落任何一条转移的边 在这里插入图片描述

代码:

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

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

KMP + 状态机

设计密码

题目链接

解题思路: 刚开始我的想法是f[i][j]f[i][j]表示第i位字符的时候,该字符是子串的字符和不是子串的字符的数量。不过这样就有一个问题:f[i][1]f[i][1]表示第i位填的是子串中的字符,那么能转移到它的状态可以是f[i1][0]f[i1][1]f[i-1][0] 和 f[i-1][1]如果是后者我们无法知道i-1位的是子串的哪一个字符,那么我们就无法知道能填的字符数量。因此我们需要增加维度,那么第二维的含义就很关键,如果仅仅表示的是子串中的字符,我们还是无法辨别是否与子串相同,那么我们就可以借助KMP中的f数组:的含义定义第二维,那么有(因为该题是与子串相关的,而KMP就是处理子串匹配的)

在这里插入图片描述 代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 55 , mod = 1e9 + 7;
int n;
char s[N];
int f[N][N];

int main(){
    cin>>n>>s+1;
    int len = strlen(s+1);
    int next[N] = {0};
    for(int i = 2 , j = 0  ; i <= len ; ++i){
        while(j && s[j+1] != s[i]) j = next[j];
        if(s[j+1] == s[i])j++;
        next[i] = j;
    }
    f[0][0] = 1;
    for(int i = 0; i <n ; ++i)
        for(int j = 0 ; j < len ; ++j)
            for(int k = 'a' ; k <= 'z' ; ++k){
                int u = j ; 
                while(u && k != s[u+1]) u = next[u];
                if(s[u+1] == k)u++;
                if(u < len) f[i+1][u] = (f[i+1][u] + f[i][j]) % mod; 
            }
    int res = 0;
    for(int i = 0 ; i < len ; ++i) res = (res + f[n][i]) % mod;
    cout<<res<<endl;
    return 0;
}

AC自动机 + 状态机

修复DNA

题目转送门

解题思路: 首先这个题和上一题的解题思路是类似的,其次为什么想到AC自动机呢,因为AC自动机就是解决多串匹配的问题在这里插入图片描述 至于动态规划的过程我们类似上一题 在这里插入图片描述 这题我们分为AC自动机的建立与动态规划计算两个部分来解决。

  1. AC自动机的建立:1、依据每一个子串建立Trie树(利用插入函数),2、从树根开始向下依次计算出每一个节点的后缀节点 (建立AC自动机的过程)
  2. 动态规划状态的计算

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 1010 , INF = 0x3f3f3f3f;
int n , m;
int tr[N][4] , dar[N] ,idx; //Trie 
int q[N] , pre[N]; //AC自动机
char str[N] ;
int f[N][N];

int get(char c){
    if(c == 'A')return 0;
    if(c == 'T')return 1;
    if(c == 'G')return 2;
    return 3;
}

void insert(){
    int p = 0;
    for(int i = 0 ; str[i] ; ++i){
        int k = get(str[i]);
        if(!tr[p][k])tr[p][k] = ++idx;
        p = tr[p][k]; 
    }
    dar[p] = 1;
}
  
void build()
{
    int hh = 0 , tt  = -1;
    for(int i = 0 ; i < 4 ; ++i)
        if(tr[0][i]) q[++tt] = tr[0][i];
    
    while(hh <= tt)
    {
        int t = q[hh++];
        for(int i = 0 ; i < 4 ;++i){
            int p = tr[t][i];
            if(!p) tr[t][i] = tr[pre[t]][i];
            else{
                pre[p] = tr[pre[t]][i];
                q[++tt] = p;
                dar[p] |= dar[pre[p]];
            }
        }
    }
}

int main(){
    int T = 1;

    while(cin>>n , n ){
        memset(tr , 0 ,sizeof tr);
        memset(dar , 0 , sizeof dar);
        memset(pre , 0 ,sizeof pre);
        idx = 0;
        for(int i = 1 ; i <= n ; ++i){
            cin>>str;
            insert();
        }
        build();//建立AC自动机
        
        cin>>str+1;
        m = strlen(str+1);
        memset(f , 0x3f ,sizeof f);
        f[0][0] = 0;
        for(int  i = 0 ; i < m ; ++i)
            for(int j = 0 ; j <= idx ; ++j)
                for(int k = 0 ; k < 4 ; ++k){
                    int t = get(str[i+1]) != k;
                    int p = tr[j][k];
                    if(!dar[p])
                        f[i+1][p] = min(f[i+1][p] ,f[i][j] + t);
                }
        int res = INF;
        for(int i = 0 ; i <= idx ; ++i )res = min(res ,f[m][i]);
        
        if(res == INF)res = -1;
        printf("Case %d: %d\n",T++,res);
    }
    
    return 0;
}