状态机DP例题

引入

我认为,状态机DP很常见,通常跟其他DP混合在一起;

一个显著特征就是,我们需要当前点的状态

或者说,我们状态之间的转移,是与我们当前点的状态是有关系的;

假设当前点为 A 1 A_1 A1,那么只能从状态 B 1 B_1 B1转移

而不能从状态 B 2 B_2 B2转移;

例题

大盗阿福

题面

大盗阿福
在这里插入图片描述
在这里插入图片描述

思路

f ( i , j ) f(i,j) f(i,j)表示前 i i i个店铺,当前状态为 j j j的所有抢法中的最大值;

如果当前店铺要抢,那么前一个必然不能抢;

f ( i , 1 ) = f ( i − 1 , 0 ) + a [ i ] f(i,1)=f(i-1,0)+a[i] f(i,1)=f(i1,0)+a[i]

如果当前不抢,那么前一个抢不抢无所谓;

f ( i , 0 ) = m a x ( f ( i − 1 , 0 ) , f ( i − 1 , 1 ) ) f(i,0)=max(f(i-1,0),f(i-1,1)) f(i,0)=max(f(i1,0),f(i1,1))

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

int f[N][2];

int a[N];

void solve(){
    int n;
    cin >> n;
    for(int i=1;i<=n;++i){
        cin >> a[i];
        f[i][0] = f[i][1] = 0;
    }
    f[0][0] = 0,f[0][1] = -1e9;//f(0,1)是非法状态,直接给个-INF
    for(int i=1;i<=n;++i){
        f[i][0] = max(f[i-1][0],f[i-1][1]);
        f[i][1] = f[i-1][0] + a[i];
    }
    cout << max(f[n][0],f[n][1]) << '\n';
}

int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int t;
    cin >> t;
    while(t--)
        solve();
    return 0;
}

股票买卖IV

股票买卖IV

题面

在这里插入图片描述
在这里插入图片描述

思路

f ( i , j , k ) f(i,j,k) f(i,j,k)表示前 i i i只股票,交易成功 j j j次,当前状态为 k k k的所有方案中的最大值;

其中 k = 0 k=0 k=0 表示手中没有股票, k = 1 k=1 k=1 表示手中有股票;

具体转移如下图
在这里插入图片描述
需要注意的是,买入加上卖出才算一次完整交易

假设我们先不考虑交易次数,或者说现在允许我们进行无限次交易;

转移方程如下:

f ( i , 0 ) = m a x { f ( i − 1 , 0 ) , f ( i − 1 , 1 ) + a [ i ] } f(i,0) = max\{f(i-1,0),f(i-1,1)+a[i]\} f(i,0)=max{f(i1,0),f(i1,1)+a[i]};

f ( i , 1 ) = m a x { f ( i − 1 , 0 ) − a [ i ] , f ( i − 1 , 1 ) } f(i,1)=max\{f(i-1,0)-a[i],f(i-1,1)\} f(i,1)=max{f(i1,0)a[i],f(i1,1)}

限制交易次数同理,只不过多一维度而已;

f [ i ] [ j ] [ 0 ] = { f [ i − 1 ] [ j ] [ 0 ] , f [ i − 1 ] [ j − 1 ] [ 1 ] + a [ i ] } m a x f[i][j][0] = \{f[i-1][j][0],f[i-1][j-1][1]+a[i]\}_{max} f[i][j][0]={f[i1][j][0],f[i1][j1][1]+a[i]}max

f [ i ] [ j ] [ 1 ] = { f [ i − 1 ] [ j ] [ 1 ] , f [ i − 1 ] [ j ] [ 0 ] − a [ i ] } m a x f[i][j][1] = \{f[i-1][j][1],f[i-1][j][0]-a[i]\}_{max} f[i][j][1]={f[i1][j][1],f[i1][j][0]a[i]}max

注意,买入是不算一次新完成的交易的;

而卖出是算一次新完成的交易的;

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

int f[N][110][2];

int a[N];

const int INF = 1e9;

void solve(){
    int n,m;
    cin >> n >> m;
    for(int i=1;i<=n;++i) cin >> a[i];
    //初始化
    for(int i=0;i<=n;++i)
        for(int j=0;j<=m;++j){
            if(j == 0) f[i][j][0] = 0,f[i][j][1] = -INF;
            else f[i][j][0] = f[i][j][1] = -INF;
        }
    for(int i=1;i<=n;++i){
        for(int j=0;j<=m;++j){
            f[i][j][0] = f[i-1][j][0];
            if(j>0) f[i][j][0] = max(f[i][j][0],f[i-1][j-1][1]+a[i]);
            f[i][j][1] = max(f[i-1][j][1],f[i-1][j][0]-a[i]);
        }
    }
    int ans = 0;
    //至多m次,具体几次不知道
    for(int j=0;j<=m;++j){
        //最后手里不持有肯定比持有赚
        ans = max(ans,f[n][j][0]);
    }
    cout << ans << '\n';
}

int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    solve();
    return 0;
}

股票买卖V

题面

股票买卖V
在这里插入图片描述
在这里插入图片描述

思路

在这里插入图片描述
这道题其实比上一题简单,只不过多加了一个状态;

f ( i , k ) f(i,k) f(i,k)表示前 i i i天,当前状态为 k k k的所有买法中的最大值;

k = 0 k=0 k=0 表示当前手上没有股票,不允许买入;
k = 1 k=1 k=1 表示手上有股票
k = 2 k=2 k=2 表示手上没有股票,允许买入;

状态转移方程来源请看上方自动机的图片;

f [ i ] [ 1 ] = m a x ( f [ i − 1 ] [ 1 ] , f [ i − 1 ] [ 2 ] − a [ i ] ) ; f[i][1] = max(f[i-1][1],f[i-1][2] - a[i]); f[i][1]=max(f[i1][1],f[i1][2]a[i]);
f [ i ] [ 2 ] = m a x ( f [ i − 1 ] [ 0 ] , f [ i − 1 ] [ 2 ] ) ; f[i][2] = max(f[i-1][0],f[i-1][2]); f[i][2]=max(f[i1][0],f[i1][2]);
f [ i ] [ 0 ] = m a x ( f [ i ] [ 0 ] , f [ i − 1 ] [ 1 ] + a [ i ] ) ; f[i][0] = max(f[i][0],f[i-1][1] + a[i]); f[i][0]=max(f[i][0],f[i1][1]+a[i]);

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

//0表示手上没股票的第一天
//1表示手上有股票
//2表示手上没股票的第二天及以上
int f[N][3];

int a[N];

const int INF = 1e9;

void solve(){
    int n;
    cin >> n;
    for(int i=1;i<=n;++i) cin >> a[i];
    //初始化
    for(int i=0;i<=n;++i)
        f[i][2] = f[i][0] = f[i][1] = -INF;
    f[0][2] = 0;
    for(int i=1;i<=n;++i){
        f[i][1] = max(f[i-1][1],f[i-1][2] - a[i]);
        f[i][2] = max(f[i-1][0],f[i-1][2]);
        f[i][0] = max(f[i][0],f[i-1][1] + a[i]);   
    }
    cout << max(f[n][0],f[n][2]) << '\n';
}

int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    solve();
    return 0;
}

设计密码

题面

设计密码
在这里插入图片描述
在这里插入图片描述

思路

f ( i , j ) f(i,j) f(i,j)表示在主串中匹配到第 i i i位(还没匹配成功,扫到 i + 1 i+1 i+1时就匹配成功了),在子串的KMP数组位于第 j j j位;

这个 j j j也可以解释成和子串 T T T匹配到第 j j j位;


K M P KMP KMP算法中,如果子串的长度为 m m m,那么走到 m m m就算匹配成功了;

我们这里想要匹配失败,那么我们就不能匹配到第 m m m位;


状态转移的话,我们可以考虑对于每一个 < i , j > <i,j> <i,j>对,枚举 i i i的所有可能取值;

假设当前主串的第 i i i个字符的取值为 c h ch ch

这个 c h ch ch会跳到 K M P KMP KMP数组的第 u u u位;

那么有 f ( i , u ) + = f ( i − 1 , j ) f(i,u) += f(i-1,j) f(i,u)+=f(i1,j)

f ( i − 1 , j ) f(i-1,j) f(i1,j)是可以转移到 f ( i , u ) f(i,u) f(i,u)的,是有贡献的;

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e2 + 10;

const int MOD = 1e9 + 7;

int n,m,nex[N];

ll f[N][N];//f(i,j)表示当前匹配到第i位(还未匹配成功),在子串的KMP数组中的位置是j

char str[N];

void solve(){
    cin >> n;
    cin >> (str+1);
    m = strlen(str+1);
    for(int i=2,j=0;i<=m;++i){
        while(j && str[i] != str[j+1]) j = nex[j];
        if(str[i] == str[j+1]) ++j;
        nex[i] = j;
    }
    f[0][0] = 1;
    for(int i=1;i<=n;++i){
        //如果匹配到m 就说明包含子串T了
        for(int j=0;j<m;++j){
            for(int k=0;k<26;++k){
                char ch = 'a' + k;
                int u = j;
                while(u && ch != str[u+1]) u = nex[u];
                if(ch == str[u+1]) ++u;
                if(u<m){
                    f[i][u] += f[i-1][j];
                    f[i][u] %= MOD;
                }
            }
        }
    }
    ll ans = 0;
    for(int j=0;j<m;++j){
        ans += f[n][j];
        ans %= MOD;
    }
    cout << ans << '\n';
}

int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    solve();
    return 0;
}

优化

我们可以预处理每个 k k k是怎么转移的,这样就可以直接转移;

而不用暴力的去跑 K M P KMP KMP了;

_ n e x [ j ] [ k ] \_nex[j][k] _nex[j][k]表示匹配到子串的第 j j j,如果主串是 k + ′ a ′ k+'a' k+a K M P KMP KMP后的值是多少;

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e2 + 10;

const int MOD = 1e9 + 7;

int n,m,nex[N],_nex[N][N];

ll f[N][N];//f(i,j)表示当前匹配到第i位(还未匹配成功),在子串的KMP数组中的位置是j

char str[N];

void solve(){
    cin >> n;
    cin >> (str+1);
    m = strlen(str+1);
    for(int i=2,j=0;i<=m;++i){
        while(j && str[i] != str[j+1]) j = nex[j];
        if(str[i] == str[j+1]) ++j;
        nex[i] = j;
    }
    for(int j=0;j<m;++j){
        for(int k=0;k<26;++k){
            char ch = k + 'a';
            int u = j;
            while(u && ch != str[u+1]) u = nex[u];
            if(ch == str[u+1]) ++u;
            _nex[j][k] = u;
        }
    }
    f[0][0] = 1;
    for(int i=1;i<=n;++i){
        //如果匹配到m 就说明包含子串T了
        for(int j=0;j<m;++j){
            for(int k=0;k<26;++k){
                char ch = 'a' + k;
                int u = _nex[j][k];
                if(u < m){
                    f[i][u] += f[i-1][j];
                    f[i][u] %= MOD;
                }
            }
        }
    }
    ll ans = 0;
    for(int j=0;j<m;++j){
        ans += f[n][j];
        ans %= MOD;
    }
    cout << ans << '\n';
}

int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    solve();
    return 0;
}

与其他DP混合的情况

E2. Rubik’s Cube Coloring (hard version)

皇宫看守

关路灯

遇到再补…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值