【DP学习总结】状态机模型

本文详细介绍了使用动态规划方法解决一系列股票买卖问题,包括不限制交易次数、限制最多交易次数、考虑冷冻期以及交易手续费的情况。此外,还探讨了结合KMP算法设计密码、利用AC自动机处理文本生成以及修复DNA序列,以及矩阵快速幂在解决大规模字符串避免子串问题中的应用。这些问题展示了动态规划与字符串处理技术在实际问题中的巧妙应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

股票买卖


股票买卖Ⅱ

题目链接:Acwing 1055

题意: 给定一个长度为 N N N的数组,数组中的第 i i i 个数字表示一个给定股票在第 i i i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

  • sol:
    定义两个状态:未持股票0,持股票1
    f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i天,第 i i i天持股状态为 j ∈ ( 0 , 1 ) j\in (0, 1) j(0,1)的所有方案的最大收益
    未持股票状态:
    1.0->1买入 2.0->0继续观望
    持股票状态:
    1.1->0卖出 2.1->1继续观望
    状态计算:
    f [ i ] 0 ] = m a x ( f [ i − 1 ] [ 0 ] , f [ i − 1 ] [ 1 ] + w [ i ] ) f [ i ] [ 1 ] = m a x ( f [ i − 1 ] [ 1 ] , f [ i − 1 ] [ 0 ] − w [ i ] ) f[i]0] = max(f[i - 1][0], f[i-1][1] + w[i])\\ f[i][1] = max(f[i-1][1], f[i-1][0]-w[i]) f[i]0]=max(f[i1][0],f[i1][1]+w[i])f[i][1]=max(f[i1][1],f[i1][0]w[i])
    具体细节看代码:

code

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, f[2][2], w[N]; //滚动数组优化空间(其实没必要hh
int main()
{
    int n; cin >> n;
    for(int i = 1; i <= n; ++ i) {
        cin >> w[i];
    }
    f[0][1] = -0x3f3f3f3f;// 由于第一天不能卖出,所以要设置0天持股状态的最大收益为负无穷
    for(int i = 1; i <= n; ++ i) {
        f[i & 1][0] = max(f[i - 1 & 1][0], f[i - 1 & 1][1] + w[i]);
        f[i & 1][1] = max(f[i - 1 & 1][1], f[i - 1 & 1][0] - w[i]);
    }
    cout << f[n & 1][0] << "\n";
}

股票买卖Ⅳ

题目链接:Acwing 1057
交易次数变成了至多 k k k

  • sol:
    定义 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]为前 i i i天,完成了 j j j笔完整交易,第 i i i天的决策是 k ∈ ( 0 , 1 ) k\in(0,1) k(0,1)的所有方案的最大收益
    状态计算:
    f [ i ] [ j ] [ 0 ] = m a x ( f [ i − 1 ] [ j ] [ 0 ] , f [ i − 1 ] [ j − 1 ] [ 1 ] + w [ i ] ) f [ i ] [ j ] [ 1 ] = m a x ( f [ i − 1 ] [ j ] [ 1 ] , f [ i − 1 ] [ j ] [ 0 ] − w [ i ] ) f[i][j][0] = max(f[i-1][j][0],f[i-1][j-1][1] +w[i])\\ f[i][j][1]=max(f[i-1][j][1],f[i-1][j][0]-w[i]) f[i][j][0]=max(f[i1][j][0],f[i1][j1][1]+w[i])f[i][j][1]=max(f[i1][j][1],f[i1][j][0]w[i])

code:

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;

int f[2][110][2];
int n, k;

int main()
{
    cin >> n >> k;
    memset(f, -0x3f, sizeof f);
    f[0][0][0] = 0;
    for(int i = 1; i <= n; ++ i) {
        int x; cin >> x;
        for(int j = 0; j <= k; ++ j) {
            f[i & 1][j][0] = f[i - 1 & 1][j][0];
            if(j) f[i & 1][j][0] = max(f[i & 1][j][0], f[i - 1 & 1][j - 1][1] + x);
            f[i & 1][j][1] = max(f[i - 1 & 1][j][0] - x, f[i - 1 & 1][j][1]);
        }
    }
    int res = 0;
    for(int i = 0; i <= k; ++ i) res = max(res, f[n & 1][i][0]);
    cout << res << "\n";
}

股票买卖 V

题目链接:Acwing 1059
在Ⅱ的条件下,增加了冷冻期:卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)

  • sol:
    在Ⅱ的情况下,增加一个状态冷冻期,如下:
    定义三个状态:未持股票0,持股票1,冷冻期2
    f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i天,第 i i i天持股状态为 j ∈ ( 0 , 1 , 2 ) j\in (0, 1, 2) j(0,1,2)的所有方案的最大收益
    未持股票状态:
    1.0->1买入 2.0->0继续观望
    持股票状态:
    1.1->2卖出 2.1->1继续观望
    冷冻期状态:
    2->0回到未持股状态
    状态计算:
    f [ i ] 0 ] = m a x ( f [ i − 1 ] [ 0 ] , f [ i − 1 ] [ 2 ] ) f [ i ] [ 1 ] = m a x ( f [ i − 1 ] [ 1 ] , f [ i − 1 ] [ 0 ] − w [ i ] ) f [ i ] [ 2 ] = f [ i − 1 ] [ 1 ] + w [ 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] f[i]0]=max(f[i1][0],f[i1][2])f[i][1]=max(f[i1][1],f[i1][0]w[i])f[i][2]=f[i1][1]+w[i]
    code:
#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
int f[2][3], n;

int main()
{
    cin >> n;
    f[0][1] = -0x3f3f3f3f;
    for(int i = 1; i <= n; ++ i) {
        int x; cin >> x;
        f[i & 1][0] = max(f[i - 1 & 1][0], f[i - 1 & 1][2]);
        f[i & 1][1] = max(f[i - 1 & 1][1], f[i - 1 & 1][0] - x);
        f[i & 1][2] = max(f[i - 1 & 1][2], f[i - 1 & 1][1] + x);
    }
    cout << max(f[n & 1][0], f[n & 1][2]) << "\n";
}

股票买卖 VI

题目链接:Acwing 1059
在Ⅱ的基础上,每次买卖有手续费 f f f

  • sol:
    与股票买卖Ⅱ同样定义,注意在卖出的时候算上手续费即可

code:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, f[2][2], w[N]; //滚动数组优化空间(其实没必要hh
int main()
{
    int n, F; cin >> n >> F;
    for(int i = 1; i <= n; ++ i) {
        cin >> w[i];
    }
    f[0][1] = -0x3f3f3f3f;// 由于第一天不能卖出,所以要设置0天持股状态的最大收益为负无穷
    for(int i = 1; i <= n; ++ i) {
        f[i & 1][0] = max(f[i - 1 & 1][0], f[i - 1 & 1][1] + w[i] - F);
        f[i & 1][1] = max(f[i - 1 & 1][1], f[i - 1 & 1][0] - w[i]);
    }
    cout << f[n & 1][0] << "\n";
}

DP+KMP


设计密码

题目链接:Acwing 1052
题意:
你现在需要设计一个密码 S S S S S S 需要满足:

  • S S S 的长度是 N N N
  • S S S 只包含小写英文字母;
  • S S S 不包含子串 T T T

请问共有多少种不同的密码满足要求?答案对 1 e 9 + 7 1e9+7 1e9+7取模

对于此题,有两个扩展,一个是将 n n n扩大到了 1 0 9 10^9 109,一个是将子串数量增加。分别对应下文的GT考试修复DNA
solution:
假设已经成功构造了长度为 i i i的密码,在构造 i + 1 i+1 i+1位的时候,如果出现了子串 T T T,那么一定是密码的后缀当中出现了子串 T T T。然后就可以进行愉快的 DP了

  • 状态定义: f [ i ] [ j ] f[i][j] f[i][j] 表示成功构造了密码为 i i i位,且后缀中匹配到子串位置是 j j j的方案数
  • 状态计算: f [ i + 1 ] [ j ] + = f [ i ] [ j ] f[i+1][j] += f[i][j] f[i+1][j]+=f[i][j](这里是枚举了第 i + 1 i+1 i+1位的字母,然后去进行kmp匹配,具体看代码)

code:

#include <bits/stdc++.h>
using namespace std;
const int N = 55, mod = 1e9 + 7;
char s[N];
int f[N][N], ne[N], n;

int main()
{
    cin >> n >> s + 1;
    int m = strlen(s + 1);
    for(int i = 2, j = 0; i <= m; ++ i) {
        while( j && s[i] != s[j + 1]) j = ne[j];
        if(s[i] == s[j + 1]) ++ j;
        ne[i] = j;
    }
    f[0][0] = 1; // 初始化
    for(int i = 0; i < n; ++ i) {
        for(int j = 0; j < m; ++ j) { // 枚举所有状态
            for(char ch = 'a'; ch <= 'z'; ++ ch) {
                int now = j; // 对每个状态,在后面加上字符ch后,进行一下kmp匹配
                while(now && s[now + 1] != ch) now = ne[now];
                if(s[now + 1] == ch) ++ now;
                if(now < m) { // 没有匹配到子串
                    f[i + 1][now] = (f[i + 1][now] + f[i][j]) % mod;
                }
            }
        }
    }
    int res = 0;
    for(int i = 0; i < m; ++ i) res = (res +f[n][i]) % mod;
    cout << res << "\n";
}

DP+AC自动机


文本生成器

题目链接:LibreOj 10063

题意:
n n n个单词,问长度为 m m m的文章中包括这些单词的数量。答案对 1 0 4 + 7 10^4+7 104+7取模。仅包含大写字母
solution:
答案容易转化为: 2 6 m − 26^m- 26m长度为 m m m的文章不包含这个 n n n单词
如何求长度为 m m m的文章不包含这个 n n n单词:
上kmp相似,将 n n n个单词构建ac自动机(trie图)。

  • 状态定义: f [ i ] [ j ] f[i][j] f[i][j]表示长度为 i i i的文章,在trie图上的状态为 j j j的方案数
  • 状态计算: f [ i + 1 ] [ p ] + = f [ i ] [ j ] , p = t r i e [ j ] [ k ] f[i+1][p] += f[i][j], p = trie[j][k] f[i+1][p]+=f[i][j],p=trie[j][k]具体看代码

code:

#include <bits/stdc++.h>
using namespace std;

const int N = 6010, M = 110, mod = 10007;

int n, m;
int trie[N][26], fail[N], idx;
bool used[N];
char str[M];

int f[M][N];
// 构建AC自动机
void insert() {
    int p = 0;
    for(int i = 0; str[i]; ++ i) {
        int u = str[i] - 'A';
        if(!trie[p][u]) trie[p][u] = ++ idx;
        p = trie[p][u];
    }  
    used[p] = true;
}

void build() {
    queue<int> q;
    for(int i = 0; i < 26; ++ i) {
        if(trie[0][i]) q.push(trie[0][i]);
    }
    
    while(q.size()) {
        auto t = q.front(); q.pop();
        
        for(int i = 0; i < 26; ++ i) {
            int p = trie[t][i];
            if(!p) trie[t][i] = trie[fail[t]][i];
            else {
                q.push(p);
                fail[p] = trie[fail[t]][i];
                used[p] |= used[fail[p]];
            }
        }
    }
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; ++ i ){
        cin >> str;
        insert();
    }
    
    build();
    
    f[0][0] = 1; // 初始化
    for(int i = 0; i < m; ++ i) { 
        for(int j = 0; j <= idx; ++ j) { // 枚举ac自动机的所有状态进行更新
            for(int k = 0; k < 26; ++ k) { // 枚举所有结点
                int p = trie[j][k];
                if(used[p]) continue; // 如果有单词则直接跳过
                f[i + 1][p] = (f[i + 1][p] + f[i][j]) % mod; // 更新答案
            }
        }
    }
    int res = 1, del = 0;;
    for(int i = 1; i <= m; ++ i) res = res * 26 % mod;
    for(int i = 0; i <= idx; ++ i) del = (del + f[m][i]) % mod;
    cout << (res - del + mod) % mod;
    
}

修复DNA

题目链接:Acwing 1053
题意:
n n n个有害DNA序列(长度不超过20),然后给出一个DNA序列(长度不超过1000)。DNA序列只包含(‘A’,‘G’,‘C’,‘T’)问将这个DNA序列修复成不含有有害DNA要改变的最少字符。

solution:
遇上题dp定义差不多,将构造改成了修改。假设已经修改好了 i i i位,在修改第 i + 1 i+1 i+1位的时候,进行状态转移

  • 状态表示: f [ i ] [ j ] f[i][j] f[i][j] 表示 长度为 i i i的DNA片段,在trie图当中的状态为 j j j的最小操作数
  • 状态计算: f [ i + 1 ] [ p ] = m i n ( f [ i + 1 ] [ p ] , f [ i ] [ j ] + k = = s t r [ i + 1 ] ) ( k ∈ ( A , G , C , T ) f[i + 1][p] = min(f[i+1][p], f[i][j]+k == str[i+1])(k \in (A,G,C,T) f[i+1][p]=min(f[i+1][p],f[i][j]+k==str[i+1])(k(A,G,C,T) 具体看代码

code:

#include <bits/stdc++.h>
using namespace std;
const int N = 1010, M = 22 * 55;
char s[M], str[N];
int f[N][N];
int tr[M][5], fail[M], idx;
bool used[M];
int n, m;
int get(char ch) {
    if(ch == 'A') return 1;
    if(ch == 'C') return 2;
    if(ch == 'G') return 3;
    if(ch == 'T') return 4;
}
// AC自动机模板
void insert()
{
    int p = 0;
    for(int i = 0; s[i]; ++ i) {
        int u = get(s[i]);
        if(!tr[p][u]) tr[p][u] = ++ idx;
        p = tr[p][u];
    }
    used[p] = true;
}

void build()
{
    queue<int> q;
    for(int i = 1; i <= 4; ++ i) {
        if(tr[0][i]) q.push(tr[0][i]);
    }
    while(q.size())
    {
        int t = q.front(); q.pop();
        for(int i = 1; i <= 4; ++ i)  {
            int p = tr[t][i];
            if(!p) tr[t][i] = tr[fail[t]][i];
            else {
                fail[p] = tr[fail[t]][i];
                used[p] |= used[fail[p]]; // 如果一个结点不是“有害”结点,但是他的fail数组指向了“有害”结点,
                						//说明他的后缀存在有害DANA
                q.push(p);
            }
        }
    }
}

int main()
{
    int Cas = 1;
    while(cin >> n && n) {
        memset(tr, 0, sizeof tr); idx = 0; 
        memset(fail, 0, sizeof fail);
        memset(used, 0, sizeof used); 
        memset(f, 0x3f, sizeof f);
        for(int i= 1; i <= n; ++ i) {
            cin >> s;
            insert();
        }
        build();  
        cin >> str + 1;
        m = strlen(str + 1);
        f[0][0] = 0; // 初始化
        for(int i = 0; i < m; ++ i) { 
            for(int j = 0; j <= idx; ++ j) { // 枚举一下trie图的所有结点(状态)
                for(int k = 1; k <= 4; ++ k) {
                    int p = tr[j][k]; // 当前结点 不是有害的
                    if(used[p]) continue; // 进行状态转移,若干原DNA不是k,则需+1
                    f[i + 1][p] = min(f[i + 1][p], f[i][j] + (k != get(str[i + 1])));
                }
            }
        }
        int res = 0x3f3f3f3f;
        for(int i = 0; i <= idx; ++ i) res = min(res, f[m][i]);
        cout << "Case " << Cas ++ << ": ";
        cout << (res == 0x3f3f3f3f ? -1 : res) << "\n";
    }
}

与矩阵快速幂的结合

GT考试

前置题目是:上文的设计密码
题目链接:Acwing 1305

题意与设计密码类似,求出长度为 n n n的序列中不包括子串的所有方案。注意这里的 n n n达到了 1 0 9 10^9 109
分析:同设计密码定义 f ( i , j ) f(i, j) f(i,j)。能够发现:
f ( i + 1 , 0 ) = a 0 , 0 ∗ f ( i , 0 ) + a 1 , 0 ∗ f ( i , 1 ) , + ⋯ + , a m − 1 , 0 ∗ f ( i , m − 1 ) ⋯ f ( i + 1 , m − 1 ) = a 0 , m − 1 ∗ f ( i , 0 ) + a 1 , m − 1 ∗ f ( i , 1 ) , + ⋯ + , a m − 1 , m − 1 ∗ f ( i , m − 1 ) f(i+1, 0) = a_{0,0}*f(i,0)+a_{1,0}*f(i, 1),+\cdots+, a_{m-1,0}*f(i,m-1)\\ \cdots \\ f(i+1, m-1)=a_{0,m-1}*f(i,0)+a_{1,m-1}*f(i, 1),+\cdots+, a_{m-1,m-1}*f(i,m-1) f(i+1,0)=a0,0f(i,0)+a1,0f(i,1),++,am1,0f(i,m1)f(i+1,m1)=a0,m1f(i,0)+a1,m1f(i,1),++,am1,m1f(i,m1)
其中, a i , j a_{i,j} ai,j为,在向下一位计算的时候,是否能从 i i i状态转移到 j j j状态,也就是说对于一个子串来说, a i . j a_{i.j} ai.j是固定的。
也就是: f ( i + 1 ) = f ( i ) ∗ A f(i+1) = f(i) * A f(i+1)=f(i)A
A = [ a 0 , 0 a 0 , 1 ⋯ a 0 , m − 1 a 1 , 0 a 1 , 1 ⋯ a 1 , m − 1 ⋯ ⋯ ⋯ ⋯ a m − 1 , 0 a m − 1 , 1 ⋯ a m − 1 , m − 1 ] A=\left[ \begin{matrix} a_{0,0} & a_{0,1} & \cdots & a_{0,m-1} \\ a_{1,0} & a_{1,1} & \cdots &a_{1,m-1} \\ \cdots & \cdots &\cdots&\cdots \\ a_{m-1,0} &a_{m-1,1} & \cdots &a_{m-1,m-1} \end{matrix} \right] A=a0,0a1,0am1,0a0,1a1,1am1,1a0,m1a1,m1am1,m1
要求长度为 n n n的只需求: f ( 0 ) ∗ A n f(0) * A^n f(0)An
f ( 0 ) = ( 1 , 0 , ⋯   , 0 ) f(0) = (1, 0, \cdots,0) f(0)=(1,0,,0)
矩阵快速幂知识
具体来看代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 25;

char str[N];
int ne[N];
int a[N][N], base[N][N];
int n, m, mod;
// 矩阵乘法
void mul(int a[][N], int b[][N])
{
    static int c[N][N];
    memset(c, 0, sizeof c);
    for(int i = 0; i < N; ++ i) {
        for(int j = 0; j < N; ++ j) {
            for(int k = 0; k < N; ++ k) {
                c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % mod;
            }
        }
    }
    memcpy(a, c, sizeof c);
}

int qpow(int n)
{
// 构建base即A矩阵
    for(int j = 0; j < m; ++ j) {
        for(char ch = '0'; ch <= '9'; ++ ch) {
            int now = j;
            while(now && str[now + 1] != ch) now = ne[now];
            if(str[now + 1] == ch) ++ now;
            if(now < m) base[j][now] ++ ;
        }
    }
    a[0][0] = 1; // 初始F(0)
    while(n) {
        if(n & 1) mul(a, base);
        n >>= 1;
        mul(base, base);
    }
    int res = 0; // 统计答案
    for(int i = 0; i < m; ++ i) res = (res + a[0][i]) % mod;
    return res;
}

int main()
{
    cin >> n >> m >> mod;
    cin >> str + 1;
    // kmp预处理
    for(int i = 2, j = 0; str[i]; ++ i) {
        while(j && str[i] != str[j + 1]) j = ne[j];
        if(str[i] == str[j + 1]) ++ j;
        ne[i] = j;
    }
    cout << qpow(n) << "\n";
}

DNA Sequence

题目链接:POJ 2778
题意:
给出 m m m个有害DNA序列,问长度为 n n n的DNA序列,不包含有害DNA序列的数量。答案对 1 0 5 10^5 105取模
n ≤ 2 × 1 0 9 , m ≤ 10 n \le 2×10^9,m\le10 n2×109,m10
solution:
根据 m m m个有害DNA序列,作出AC自动机的trie图。根据trie图作出可达矩阵。
对于可达矩阵,作 n n n次方后 a i , j a_{i,j} ai,j的值就是从 i i i号结点出发,走 j j j步的方案数。 具体细节看代码
code:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#define endl '\n' 
#define ALL(a) (a).begin(), (a).end()
#define IOS ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
using namespace std;
inline void Max(int &a, int b) { if(a < b) a = b; }
inline void Min(int &a, int b) { if(a > b) a = b; }
typedef long long LL;
const int N = 110, Mod = 100000;
char str[N];
int trie[N][4], fail[N], idx;
bool used[N];
int a[N][N], base[N][N], n, m;

int get(char s) {
    if(s == 'A') return 0;
    if(s == 'G') return 1;
    if(s == 'C') return 2;
    if(s == 'T') return 3;
}
// 构建trie图
void insert() {
    int p = 0;
    for(int i = 0; str[i]; ++ i) {
        int u = get(str[i]);
        if(!trie[p][u]) trie[p][u] = ++ idx;
        p = trie[p][u];
    }
    used[p] = true;
}

void build() {
    queue<int> q;
    for(int i = 0; i < 4; ++ i) {
        if(trie[0][i]) q.push(trie[0][i]);
    }
    while(q.size()) {
        int t = q.front(); q.pop();

        for(int i = 0; i < 4; ++ i) {
            int &p = trie[t][i];
            if(!p) p = trie[fail[t]][i];
            else {
                q.push(p);
                fail[p] = trie[fail[t]][i];
                used[p] |= used[fail[p]];
            }
        }
    }
}
// 矩阵乘法
void mul(int a[][N], int b[][N]) {
    static int c[N][N];
    memset(c, 0, sizeof c);
    // 这里如果是 < N 会TLE (很神奇
    for(int i = 0; i <= idx; ++ i) {
        for(int j = 0; j <= idx; ++ j) {
            for(int k = 0; k <= idx; ++ k) {
                c[i][j] = (c[i][j] + (LL)a[i][k] * b[k][j]) % Mod;
            }
        }
    }
    memcpy(a, c, sizeof c);
}

void solve()
{
    cin >> m >> n;
    for(int i = 1; i <= m; ++ i) {
        cin >> str;
        insert();
    }

    build();
	// 写出可达矩阵
    for(int i = 0; i <= idx; ++ i) {
        for(int j = 0; j < 4; ++ j) {
            int p = trie[i][j];
            if(!used[i] && !used[p]) base[i][p] ++;  // 当且仅当i和p结点都是无害的那么他们之间就可达
        }
    }
    a[0][0] = 1;// 初始化
    while(n) {// 矩阵快速幂
        if(n & 1) mul(a, base);
        n >>= 1;
        mul(base, base);
    }
    int res = 0; // 累计答案
    for(int i = 0; i <= idx; ++ i) res = (res + a[0][i]) % Mod;
    cout << res << "\n";
}

int main(){
    IOS;
    int T = 1;
    while(T --) solve();
    return 0;
}
### 状态机动态规划与普通动态规划的主要区别 #### 一、核心概念差异 状态机动态规划(State Machine Dynamic Programming, SM-DP)是一种基于状态转移的优化算法,其本质是利用有限状态自动机的概念来描述问题的状态及其之间的关系。相比之下,普通的动态规划(Dynamic Programming, DP)更注重于子问题分解和最优子结构[^2]。 在普通动态规划中,通常定义一个简单的状态集合以及相应的状态转移方程,这些状态往往只依赖前一步的结果或者某些固定的条件。而在状态机动态规划中,则引入了一个更加复杂的机制——即通过显式的状态转移矩阵或图谱来刻画当前状态如何转移到下一状态。这种复杂性使得SM-DP更适合解决那些具有多阶段决策过程且每步之间存在强关联性的难题[^3]。 #### 二、表达方式上的不同 对于普通DP而言,它一般采用数组形式保存中间计算所得的各种状态值;而对于状态机DP来说,除了常规意义上的数值型变量外,还经常需要用到位运算技巧来进行高效编码/解码操作以便更好地管理和更新各个离散化后的抽象层次较高的逻辑单元(比如开关灯泡问题)。因此,在实际编程实现过程中可以看到两者代码风格存在一定差距: ```python # 普通动态规划示例 dp = [0]*(n+1) for i in range(1,n+1): dp[i]=max(dp[i-1],nums[i]+dp[i-2]) print(dp[-1]) # 状态机动态规划示例 from functools import lru_cache @lru_cache(None) def dfs(mask,k): if k==m:return int(not mask&(mask<<1|1)) res=inf for j in range(n): if not (mask>>j)&1: cost=sum(A[j][i]*((mask>>i)&1)for i in range(j))+(B[j]^bool(k%2))*A[j][j] res=min(res,cost+dfs(mask^(1<<j),k+1)) return res ans=dfs(0,0);del dfs;print(ans) ``` 上述两段伪代码分别展示了两种方法解决问题时所采取的不同策略:前者仅需维护线性表记录历史最大收益即可满足需求;后者却要借助递归加记忆化搜索配合掩码技术才能妥善处理高度耦合的关系网状结构下的全局最优点寻找工作。 #### 三、适用范围对比 由于各自特点决定着它们擅长应对哪类挑战场景,所以当面临具体项目选型考量环节时就需要仔细权衡利弊得失情况再做定夺了: - **普通动态规划** 更适合应用于诸如最长公共子序列(LCS),背包问题(Knapsack Problem)等领域内的经典组合数学模型构建任务之中; - 而 **状态机动态规划**, 则因其能够有效捕捉时间轴维度变化规律的缘故,在诸如模式匹配(Pattern Matching),棋盘覆盖(Board Tiling Problems)等方面展现出独特魅力. 综上所述,虽然二者同属一类重要求解工具箱成员角色定位相似之处颇多,但由于内部运作原理层面存在着显著差别从而导致最终呈现出来的表现形态各异,进而影响到各自的专攻方向有所侧重倾向明显不一样. ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

W⁡angduoyu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值