AcWing 520. 子串 题解(转载)

题目传送门

算法

(线性DP,前缀和) \(O(nmk)\)

首先考虑如何DP,然后再考虑如何优化。

状态表示:f[i, j, k]表示只用S的前i个字母,选取了k段,可以匹配T的前j个字母的方案数。

状态计算:将f[i, j, k]表示的所有方案分成两大类:

  1. 不用S[i],则方案数是 f[i - 1, j, k]
  2. 使用S[i],那么可以按S[i]所在的一段一共有多少字母继续分类:
    • 如果有t个字母,则方案数是f[i - t, j - t, k - 1]

所以f[i, j, k] = f[i - 1, j, k] + sum(f[i - t, j - t, k - 1])
其中t只要满足S[i - t + 1] == T[j - t + 1]就可以一直往前枚举,因此最坏情况下的时间复杂度是 \(O(nm^2k)\),会超时。

接下来考虑如何优化。

我们发现f[i, j, k]第二项的表达式和f[i - 1, j - 1, k]第二项的表达式很像,具有递推关系,因此可以令sum[i, j, k] = sum(f[i - t, j - t, k - 1]),则:

  • 如果 S[i] == T[j],那么 sum[i, j, k] = sum[i - 1, j - 1, k] + f[i - 1, j - 1, k - 1]
  • 如果 S[i] != T[j],那么 sum[i, j, k] = 0

至此,时间复杂度可以降至 \(O(nmk)\)
但空间上需要 \(2nmk\) 的内存,总共需要 \(2 \times 1000 \times 200^2\)int,约 \(305\)MB,超过了内存限制。
因此需要对空间进行优化。

仔细观察转移方程,发现f[i, j, k]sum[i, j, k]均只和第i - 1层有关,因此可以使用滚动数组,同时可以发现jk只会从更小的值转移过来,因此可以使用类似于01背包问题优化空间的方式,从大到小枚举j, k,这样连滚动都可以省略了。

时间复杂度

状态总共有 \(nmk\) 个,每个状态需要常数次计算,因此总时间复杂度是 \(O(nmk)\)

C++ 代码

#include <bits/stdc++.h>

using namespace std;

const int N = 1010, M = 210, mod = 1e9 + 7;

int n, m, K;
char a[N], b[M];
int f[M][M], sum[M][M];

int main()
{
    scanf("%d%d%d", &n, &m, &K);
    scanf("%s%s", a + 1, b + 1);

    f[0][0] = 1;
    for (int i = 1; i <= n; i ++ )
        for (int j = m; j; j -- )
            for (int k = K; k; k -- )
            {
                if (a[i] == b[j]) sum[j][k] = (sum[j - 1][k] + f[j - 1][k - 1]) % mod;
                else sum[j][k] = 0;
                f[j][k] = (f[j][k] + sum[j][k]) % mod;
            }

    printf("%d\n", f[m][K]);
    return 0;
}
posted @ 2024-02-02 16:52  BadBadBad__AK  阅读(20)  评论(1编辑  收藏  举报