DP 状态机模型 AcWing算法提高课 详解

状态机模型

AcWing 1049. 大盗阿福

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#include <sstream>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 1e5 + 10;

int w[N];
int f[N][2];

int main()
{
    int _; cin >> _;
    
    while (_ -- )
    {
        int n;
        cin >> n;
        for (int i = 1; i <= n; i ++ ) cin >> w[i];
        
        // memset(f, 0, sizeof f);
        
        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] + w[i];
        }
        
        cout << max(f[n][0], f[n][1]) << endl;
    }
}

AcWing 1057. 股票买卖 IV (初始化)

在这里插入图片描述

  • 本题限制了 总交易的次数,因此不能用贪心
  • 对于第 i i i购入的股票,当且仅当第 j ( i < = j ) j(i<=j) j(i<=j)天才能卖掉,获得的利润 w j − w i w_j-w_i wjwi,由此可知这是一个线性问题
  • 以当前递推到的天数,作为线性dp的阶段,对于递推到的第 i i i天,我们需要记录的信息有:
  • 1.在完成第 i i i天的决策后,状态是持仓还是空仓
  • 2.在第 i i i天交易完成时,总共完成的完整的交易数(题目规定一次买入加一次卖出才是一场完整交易)
  • 于是,状态表示 f [ i , j , k ] f[i,j,k] f[i,j,k]表示考虑前 i i i天的股票,第 i i i天的持仓空仓状态是 k k k,且完成的完整交易次数是 j j j,属性是 m a x max max

状态机模型DP

  • 1.第i天状态是空仓状态(i==0),则第i2天产生的行为是卖出行为或者空仓行为
  • 2.第i天状态是持仓状态(i==1),则第i天产生的行为是买入行为或者持仓行为

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#include <sstream>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 1e5 + 10, M = 110;

int f[N][M][2];
int w[N];

int main()
{
    int n, k;
    cin >> n >> k;
    for (int i = 1; i <= n; i ++ ) cin >> w[i];
    
    // 注意1
    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 = 0; j <= k; j ++ )
        {
            f[i][j][0] = f[i - 1][j][0];
            if (j) f[i][j][0] = max(f[i][j][0], f[i - 1][j - 1][1] + w[i]);     // 注意2
            f[i][j][1] = max(f[i - 1][j][1], f[i - 1][j][0] - w[i]);
        }
    
    int res = 0;
    for (int i = 0; i <= k; i ++ ) res = max(res, f[n][i][0]);      // 注意3  最后一天必须是空仓
    
    cout << res << endl;
    return 0;
}

AcWing 1058. 股票买卖 V

在这里插入图片描述

  • 状态 f [ i , j ] f[i,j] f[i,j]表示考虑前i天股市,当前第i天的状态是j的方案,属性是 m a x max max
  • 如果第i天是空仓(且非冷冻)状态(0),则第i-1天是空仓或者即将冷冻 即 f [ i , 0 ] = m a x ( f [ i − 1 , 0 ] , f [ i − 1 , 2 ] ) f[i,0]=max(f[i-1,0],f[i-1,2]) f[i,0]=max(f[i1,0],f[i1,2])
  • 如果第i天是即将冷冻状态(2),则第i-1天是持仓状态 即 f [ i , 2 ] = f [ i − 1 , 1 ] + w [ i ] f[i,2]=f[i-1,1]+w[i] f[i,2]=f[i1,1]+w[i]
  • 如果第i天是持仓状态(1),则第i-1天是持仓状态或者空仓 即 f [ i , 1 ] = m a x ( f [ i − 1 , 1 ] , f [ i − 1 , 0 ] − w [ i ] ) f[i,1]=max(f[i-1,1],f[i-1,0]-w[i]) f[i,1]=max(f[i1,1],f[i1,0]w[i])
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#include <sstream>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 1e5 + 10;

int w[N];
int f[N][3];

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

AcWing 1052. 设计密码

在这里插入图片描述

  • 这题是有 ∣ T ∣ + 1 |T|+1 T+1个状态自动机
  • m个点,每个点有26条边。一个字符串不包含某个字符串,其实是表示在kmp中走不到m这个点,可以把kmp的过程看成状态机跳的过程(走到最后一个点表示匹配了,我们要不匹配,所以就是走不到最后一个点)的方案数
  • f [ i , j ] f[i,j] f[i,j]表示当前已经填到了字符串的第i位(一共填n位),j表示走到了kmp中第j个位置。那么状态数量就有N*M,每个状态有26种枚举方式。因此 26 N 2 26N^2 26N2。建图时, M ∗ 26 M*26 M26(每个状态有26种选择) ∗ N *N N(每个选择有一个while循环),所以预处理也是 26 N 2 26N^2 26N2,因此总的就是 26 N 2 26N^2 26N2
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <string.h>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#include <sstream>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 55, mod = 1e9 + 7;

int n, m;
char str[N];
int f[N][N];
int ne[N];

int main()
{
    cin >> n >> (str + 1);
    m = strlen(str + 1);
    
    for (int i = 2, j = 0; i <= n; i ++ )
    {
        while (j && str[i] != str[j + 1]) j = ne[j];
        if (str[i] == str[j + 1]) j ++ ;
        ne[i] = j;
    }
    
    f[0][0] = 1;
    
    for (int i = 0; i < n; i ++ )		// 最终串的长度
        for (int j = 0; j < m; j ++ )		// kmp数组的长度
            for (char k = 'a'; k <= 'z'; k ++ )		// 当前位置选哪个字母
            {
                int u = j;      // j需要跳到哪
                while (u && k != str[u + 1]) u = ne[u];
                if (k == str[u + 1]) u ++ ;
                if (u < m) f[i + 1][u] = (f[i + 1][u] + f[i][j]) % mod;		// u小于m就表示不匹配,那么是可以走的
            }
    
    int res = 0;
    for (int i = 0; i < m; i ++ ) res = (res + f[n][i]) % mod;      // 累加所有走不到m的状态
    cout << res << endl;
    
    return 0;
}

AcWing 1053. 修复DNA

在这里插入图片描述

  • f [ i , j ] f[i,j] f[i,j]表示前i个字母,当前走到了ac自动机中第j个位置的所有的操作方案中最少修改的字母数量
  • 建trie时要在所有结尾的地方打一个标记,然后再将trie建成ac自动机。所有打上标记的点应该是完全避开的
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <string.h>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#include <sstream>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 1010;

int n, m;
int tr[N][4], dar[N], idx;
int q[N], ne[N];
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 t = get(str[i]);
        if (tr[p][t] == 0) tr[p][t] = ++ idx;
        p = tr[p][t];
    }
    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[ne[t]][i];
            else
            {
                ne[p] = tr[ne[t]][i];
                q[ ++ tt] = p;
                dar[p] |= dar[ne[p]];
            }
        }
    }
}

int main()
{
    int T = 1;
    
    while (cin >> n, n)
    {
        memset(tr, 0, sizeof tr);
        memset(dar, 0, sizeof dar);
        memset(ne, 0, sizeof ne);
        idx = 0;
        
        for (int i = 0; i < n; i ++ )
        {
            cin >> str;
            insert();
        }
        
        build();
        
        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 = 0x3f3f3f3f;
        for (int i = 0; i <= idx; i ++ ) res = min(res, f[m][i]);
        
        if (res == 0x3f3f3f3f) res = -1;
        printf("Case %d: %d\n", T ++ , res);
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值