状态机模型

状态机代表一系列有序的事件,我们通过状态机能够将一个复杂的状态拓展为几个简单过程

状态机是一种另类的状态表示方式,实际上是我们将每一个状态拓展成一个过程

任何一个方案都能唯一对应一个状态机

简单的说,我们可以通过状态机将一个复杂、混沌的状态细分成几个清晰的状态

状态机在任意时刻的状态大多为01

状态机需要考虑入口出口

大盗阿福

阿福是一名经验丰富的大盗。趁着月黑风高,阿福打算今晚洗劫一条街上的店铺。

这条街上一共有 \(n\) 家店铺,每家店中都有一些现金。

阿福事先调查得知,只有当他同时洗劫了两家相邻的店铺时,街上的报警系统才会启动,然后警察就会蜂拥而至。

作为一向谨慎作案的大盗,阿福不愿意冒着被警察追捕的风险行窃。

他想知道,在不惊动警察的情况下,他今晚最多可以得到多少现金?

\(1≤T≤50,\)
\(1≤n≤10^5\)

题解:线性DP——状态机模型

  • 第一种非线性\(dp\)

  • 状态表示:\(f[i]\)代表只从前\(i\)个店铺中偷窃,能够获得的最大价值

  • 状态属性:\(MAX\)

  • 状态计算:

  1. 选择不偷窃第\(i\)个店铺:\(f[i-1]\)
  2. 选择偷窃第\(i\)个店铺,那我们一定无法偷窃第\(i-1\)个店铺:\(f[i-2] + w[i]\)

\[f[i] = max(f[i-1],f[i-2] + w[i]) \]

  • 状态初始:\(f[0] = 0,f[1] = w[1]\)

  • 答案呈现:\(f[n]\)

  • 第二种线性\(dp\)(状态机模型):

  • 状态表示:

\(f[i][0/1]\)代表只从前\(i\)个店铺中选,且\(0\)代表选择第\(i\)家店铺,\(1\)代表不选第\(i\)家店铺所能获得的最大价值

  • 状态属性: \(MAX\)

  • 状态计算:按照路径来划分集合,若选择某店铺,将其状态表示为\(1\),若不选某个店铺,将其状态表示为\(0\)

image-20230517002639953
  1. 选择第\(i\)个店铺,那么我们肯定不选择第\(i-1\)家店铺,即只有一条边从状态\(0\)通向状态\(1\),即\(f[i-1][0]+w[i]\)
  2. 不选择第\(i\)个店铺,那么第\(i-1\)家店铺可以选择也可以不选择,即有两条路径可以从其他状态到状态\(0\),即\(max(f[i-1][0],f[i-1][1])\)

\[f[i][1] = f[i-1][0] + w[i] \\ f[i][0] = max(f[i-1][0],f[i-1][1]) \]

  • 状态初始:\(f[1][0] = 0,f[1][1] = w[1]\)

  • 状态机入口:\(1\),状态机出口:\(0\)\(1\)

  • 答案呈现:\(max(f[n][0],f[n][1])\)

//第一种dp
const int N = 2e5 + 10, M = 4e5 + 10;

int n;
int f[N];
int w[N];

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; ++i)
        cin >> w[i];
    for (int i = 0; i <= n; ++i)
        f[i] = 0;
    f[0] = 0;
    f[1] = w[1];
    for (int i = 2; i <= n; ++i)
        f[i] = max(f[i - 1], f[i - 2] + w[i]);
    cout << f[n] << endl;
}
//第二种dp
const int N = 2e5 + 10, M = 4e5 + 10;

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

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; ++i)
        cin >> w[i], f[i][1] = f[i][0] = 0;
    for (int i = 1; i <= n; ++i)
    {
        f[i][1] = max(f[i][1], f[i - 1][0] + w[i]);
        f[i][0] = max(f[i - 1][1], f[i - 1][0]);
    }
    cout << max(f[n][0], f[n][1]) << endl;
}

股票买卖 IV

给定一个长度为 \(n\) 的数组,数组中的第 \(i\) 个数字表示一个给定股票在第 \(i\) 天的价格。

设计一个算法来计算你所能获取的最大利润,你最多可以完成 \(k\) 笔交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

一次买入卖出合为一笔交易

\(1≤n≤10^5\)

\(1≤k≤100\)

题解:线性\(dp\)——状态机模型 \(O(nk)\)

  • 状态表示

\(f[i][j][0/1]\)代表只在前\(i\)天中选择,且已经完成\(j\)笔交易,且\(1\)代表在第\(i\)天手中有股票,\(0\)代表第\(i\)天手中没有股票的最大收益

  • 状态属性:\(MAX\)

  • 状态计算:

image-20230522210348655
  1. 如果第\(i\)天手中有股票:\(f[i][j][1] = max(f[i-1][j][1],f[i-1][j][0] - w[i])\)
  2. 如果第\(i\)天手中无股票:\(f[i][j][0] = max(f[i-1][j][0],f[i-1][j-1][1] + w[i])\)
  • 状态优化:

因为是线性的,所以容易发现第一维可以优化掉,倒序遍历交易数量(第二维)

  • 状态初始:\(f[i][0][0] = 0 / f[0][0] = 0\)

  • 答案呈现:\(max\sum (f[n][j][0],f[n][j][1])\)

const int N = 2e5 + 10, M = 1e2 + 10;

int n, m;
int w[N];
int f[M][2];
// f[i][j][0/1]代表只在前i天中选择,且已经完成j笔交易,且1代表在第i天手中有股票,0代表第i天手中没有股票的最大收益

void solve()
{
    cin >> n >> m;
    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)
        for (int j = m; j >= 0; --j)
        {
            f[j][1] = max(f[j][1], f[j][0] - w[i]);
            if (j >= 1)
                f[j][0] = max(f[j][0], f[j - 1][1] + w[i]);
        }
    int ans = -INF;
    for (int j = 0; j <= m; ++j)
        ans = max({ans, f[j][0], f[j][1]});
    cout << ans << endl;
}

股票买卖Ⅴ

给定一个长度为 \(n\) 的数组,数组中的第 \(i\) 个数字表示一个给定股票在第 \(i\) 天的价格。

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

  • 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
  • 卖出股票后,你无法在第二天买入股票 (即冷冻期\(1\) 天)。

\(1 \leq n \leq 10^5\)

题解:线性\(dp\)——状态机模型

  • 状态表示:

\(f[i][0/1/2]\):代表只在前\(i\)天中选,且\(0\)代表第\(i\)天手中有股票,1代表第\(i\)天是卖出股票后的第一天,2代表第\(i\)天卖出股票后的第\(k\)天(\(k>=2\))能够获得的最大利润

  • 状态属性:\(MAX\)

  • 状态计算:

image-20230522212131316

\(f[i][0] = max(f[i-1][0],f[i-1][2] - w[i])\)
\(f[i][1] = f[i-1][0] + w[i]\)
\(f[i][2] = max(f[i-1][1],f[i-1][2])\)

  • 状态机出口:\(1/2\)

  • 状态机入口:\(2\)

  • 状态初始:\(f[0][2] = 0,f[i][j] = -INF\)

  • 答案呈现:\(max(f[n][1],f[n][2])\)

const int N = 2e5 + 10, M = 1e2 + 10;

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

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; ++i)
        cin >> w[i];
    for (int i = 0; i <= n; ++i)
        f[i][1] = f[i][0] = -INF;
    f[0][2] = 0;
    for (int i = 1; i <= n; ++i)
    {
        f[i][0] = max(f[i - 1][0], f[i - 1][2] - w[i]);
        f[i][1] = f[i - 1][0] + w[i];
        f[i][2] = max(f[i - 1][1], f[i - 1][2]);
    }
    cout << max(f[n][1], f[n][2]) << endl;
}

设计密码

你现在需要设计一个密码 \(S\)\(S\) 需要满足:

  • \(S\) 的长度是 \(n\)
  • \(S\) 只包含小写英文字母
  • \(S\) 不包含子串 \(T\)

例如:\(abc\)\(abcde\)\(abcde\) 的子串,\(abd\) 不是 \(abcde\) 的子串。

请问共有多少种不同的密码满足要求?

由于答案会非常大,请输出答案模 \(10^9+7\) 的余数。

\(1 \leq n \leq 50\)

题解:\(KMP\) + 状态机模型 \(O(26n^3)\)

  • 状态表示:

\(f[i][j]\):代表已经生成了\(i\)位密码,且第\(i\)位密码已经匹配到子串中位置为\(j\),即第\(i + 1\)位密码正在和子串中\(j+1\)位进行匹配的方案数

image-20230522223508487
  • 状态属性:数量

  • 状态转移:

因为字符串\(S\)中只存在小写字母,所以S的位置\(i + 1\)处的字符只有\(26\)种可能性

我们不妨枚举位置\(i + 1\)会出现的所有小写字母\(k\)

如果第\(i+1\)位的字母k不等于\(T[j + 1]\),我们总可以利用\(KMP\)\(next\)数组找到一个位置\(u\)使得\(k=T[u+1]\)

我们令\(u:=u + 1\),字符串\(T\)的长度为\(m\),因为\(S\)中不能含有\(T\),所以只有\(u < m\)的时候才能转移

状态转移方程:\(f[i+1][u] = (f[i+1][u] + f[i][j])\ \ \% \ \ mod \ \wedge(u < m)\)

时间复杂度:\(O(26n)\)

  • 状态初始:\(f[0][0] = 1\)

  • 答案呈现:\(\sum f[n][i]\)

const int N = 50 + 10, M = 4e5 + 10;

int n;
int ne[N];
int f[N][N];

void solve()
{
    cin >> n;
    string t;
    cin >> t;
    int m = t.length();
    t = " " + t;
    ne[1] = 0;
    for (int i = 2, j = 0; i <= m; ++i)
    {
        while (j && t[i] != t[j + 1])
            j = ne[j];
        if (t[i] == t[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 k = 'a'; k <= 'z'; ++k)
            {
                int u = j;
                while (u && t[u + 1] != k)
                    u = ne[u];
                if (t[u + 1] == k)
                    u++;
                if (u < m)
                    f[i + 1][u] = (f[i + 1][u] + f[i][j]) % mod;
            }
    int ans = 0;
    for (int j = 0; j < m; ++j)
        ans = (ans + f[n][j]) % mod;
    cout << ans << endl;
}
posted @ 2023-05-23 21:02  Zeoy_kkk  阅读(64)  评论(0编辑  收藏  举报