CF494B Obsessive String KMP+前缀和优化DP

给一份看起来字数比较少的题解?

题意

给一个字符串 \(s\),和一个字符串 \(t\)。在 \(s\) 中取出任意多个不重叠的子串 (可以有子串没有被取出),使得每个子串都包含 \(t\) ,求方案数。

具体题意见原题面。

思路

  • 由于需要每个子串中都包含 \(t\),必然需要一个字符串匹配算法,kmp 或者 hash 找出来所有的匹配位置即可,代码中用 vector<int> pos 来记录了所有位置。
  • 求方案数考虑下 dp,注意题目图片中的公式,每一小段 \(ab\) 序列,都由一个 \(b_i\) 来结尾。
  • 设计状态\(dp[i]\) 表示后面子串不取,只考虑前 \(i\) 个字符,第 \(i\) 位作为 \(b_x\) 的方案数。
  • 考虑转移:若 \(i\) 位作为 \(b_x\) ,那么我们需要找到对应的 \(a_x\) ,显然 \(a_x\)\(b_x\) 势必包含一个匹配位置,可以二分或者线性记录找到对应的最近匹配位置 \(p\)
    • 如果没有找到一个匹配位置,\(dp[i]=0\)
    • 否则有一个显然的方案,整个子串只有一个 ab 序列,即 \(dp[i] = p\)
    • 然后考虑从前面的状态转移,设 \(i\) 对应 b 序列前一个 \(b_y\)\(j\) 下标,则 dp[i] += dp[j] * (p - j)
    • 解释一下转移,对于 \(s[j+1,p]\) 位置的所有下标,都可以作为 \(b_x\) 对应的 \(a_x\) ,再用乘法原理计算。
    • 对于任意 \(j \leq p-1\) 都有以上的转移,\(dp[i] =dp[i]+ (\sum_{j=1}^{p-1} (p - j) * dp[j])\)
    • 发现可以前缀和优化,维护 \(i * dp[i]\)\(dp[i]\) 两个前缀和即可。
  • 时间复杂度 \(O(n)\),代码中的二分查找可以优化掉,这里就懒得写了
const int N = 1e5 + 10, mod = 1e9 + 7;
ll dp[N], pre[N], ipre[N];
// dp[i] 表示第 i 位作为 b_j 的方案数。pos 记录匹配起始位置
int main() {
    string s, t;
    IOS;
    cin >> s >> t;
    int n = s.size(), m = t.size();
    s = " " + s, t = " " + t;
    kmp.get_ne(t.c_str(), SZ(t) - 1);
    pos.pb(-m);
    kmp.match(s.c_str(), t.c_str(), n, m);
    for (int i = 1; i <= n; i++) {
        pre[i] = pre[i - 1];
        ipre[i] = ipre[i - 1];
        auto p = *prev(upper_bound(ALL(pos), i - m + 1));
        if (p == pos[0]) continue;
        dp[i] = p;
        dp[i] = (dp[i] + p * pre[p - 1] % mod - ipre[p - 1] + mod) % mod;
        pre[i] += dp[i];
        ipre[i] = (ipre[i] + i * dp[i]) % mod;
        if (pre[i] >= mod) pre[i] -= mod;
    }
    ll ans = 0;
    for (int i = 1; i <= n; i++) ans = (ans + dp[i]) % mod;
    printf("%lld\n", ans);
    return 0;
}
posted @ 2022-09-27 15:47  Roshin  阅读(39)  评论(0编辑  收藏  举报
-->