Codeforces Round #820 (Div. 3) G. Cut Substrings(kmp状态机dp)

TP

题意:

  • 给定两个字符串 s、t,问最少删除多少个 s 中出现的 t 串,使得 s 中不再存在任何 t 串。删除后的位置不拼接,而是变成 … 点

在这里插入图片描述

  • 同时统计最少删除的方案数。

思路:

  • 看到维护 最少 就可以联想到能不能用 dp 解决问题。但对于每一个 si 都要考虑前面是否删除过,导致当前匹配到 t 串的位置在哪,这显然就是一个 kmp状态机dp 问题。

  • 对 t 串 kmp 处理,每个 ne 指针的位置都是一个状态。

  • d p [ i ] [ j ] 的含义为: s 串匹配到前 i 个字符,对应匹配到 t 串 j 状态的最小删除数 dp[i][j]的含义为: s 串匹配到 前 i 个字符,对应匹配到 t 串 j 状态 的 最小删除数 dp[i][j]的含义为:s串匹配到前i个字符,对应匹配到tj状态的最小删除数

  • c n t [ i ] [ j ] 就是当前最小删除数维护的方案数 cnt[i][j]就是当前最小删除数维护的方案数 cnt[i][j]就是当前最小删除数维护的方案数

分类讨论:

  • 如果当前位置 s i s_i si 字母能使得 j j j 状态转移成 t o to to 状态, t o to to 状态分为匹配到 m (即匹配了 t 整串),和 0 ~ m-1 状态。
  • 匹配到 m ,整个 t 串要尝试删除。这里注意是直接判断 s i − m + 1 , i s_{i-m+1,i} sim+1,i 是否与 t 串相同,而不是判断 to 状态是否为 m,对于每个原串中匹配的位置,都考虑删除的情况,转移。
abacaba
abaca
//如果判断 状态 == m,该样例结果是 1 2 ...
  • i 由 i - m 所有可能的合法 0 ~ m-1 状态 中的最小删除数、对应方案数 转移过来。匹配后删除数 +1,位置都变成点,对应转移过去的状态应该是 0
    d p [ i + 1 ] [ 0 ] < = m i [ i + 1 − m ] + 1 dp[i+1][0] <= mi[i+1-m] + 1 dp[i+1][0]<=mi[i+1m]+1

  • 匹配到 0 ~ m-1,那就直接 i 由 i - 1 转移,状态 j 到 to
    d p [ i + 1 ] [ t o ] < = d p [ i ] [ j ] dp[i+1][to] <= dp[i][j] dp[i+1][to]<=dp[i][j]

  • 类似最短路计数一样,维护最小删除数的同时维护方案数即可。注意清空。

C o d e : Code: Code:

#include<bits/stdc++.h>
#include<unordered_map>
#define debug cout << "debug---  "
#define debug_ cout << "\n---debug---\n"
#define oper(a) operator<(const a& ee)const
#define forr(a,b,c) for(int a=b;a<=c;a++)
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define all(a) a.begin(),a.end()
#define sz(a) (int)a.size()
#define endl "\n"
#define ul (u << 1)
#define ur (u << 1 | 1)
using namespace std;

typedef unsigned long long ull;
typedef long long ll;
typedef pair<ll, int> PII;

const int N = 5e2 + 10, M = 2e6 + 10, mod = 1e9 + 7;
int INF = 0x3f3f3f3f; ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, B = 10, ki;

//kmp状态机,多状态dp
int ne[N];

int dp[N][N];//s 前 i 个字符,匹配到 j 状态 的 最小删除数
int mi[N], res[N];
int cnt[N][N];//最小删除数对应的方案数
char s[N], t[N];
bool st[N];

void solve() {
    cin >> s + 1;
    cin >> t + 1;
    n = strlen(s + 1);
    m = strlen(t + 1);

    mem(ne, 0);
    mem(st, 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;
    }

    //预处理每个位置终点 s 是否匹配 t
    for (int i = 1; i <= n; i++) {
        if (i >= m) {
            bool f = true;
            for (int j = i, len = m; j > i - m; j--, len--)
                if (t[len] != s[j])f = false;
            if (f)st[i] = true;
        }
    }

    mem(dp, 0x3f);
    mem(cnt, 0);
    dp[0][0] = 0;
    cnt[0][0] = 1;

    
    for (int i = 0; i < n; i++) {

        mi[i] = INF, res[i] = 0;
        for (int j = 0; j < m; j++) {
            if (dp[i][j] < mi[i]) {
                mi[i] = dp[i][j];
                res[i] = cnt[i][j];
            }
            else if (dp[i][j] == mi[i])
                res[i] = (res[i] + cnt[i][j]) % mod;
        }

        //匹配了 t 串、匹配到 m 的情况,由 i + 1 - m 的所有 不为 m 的 状态中维护的最小值、方案数转移过来
        //匹配后变成点,所以转移到状态 0
        if (st[i + 1]) {
            if (dp[i + 1][0] > mi[i + 1 - m] + 1) {
                dp[i + 1][0] = mi[i + 1 - m] + 1;
                cnt[i + 1][0] = res[i + 1 - m];
            }
            else if (dp[i + 1][0] == mi[i + 1 - m] + 1) {
                cnt[i + 1][0] = (cnt[i + 1][0] + res[i + 1 - m]) % mod;
            }
        }

        //由当前合法状态 [ i, j ] 推导到下一个字母 s[i + 1]
        for (int j = 0; j < m; j++) {

            int to = j;

            while (to && t[to + 1] != s[i + 1])to = ne[to];
            if (s[i + 1] == t[to + 1])to++;

            //匹配过去状态变成 to

            //只考虑不匹配到 m 的状态
            if (to != m) {
                if (dp[i + 1][to] > dp[i][j]) {
                    dp[i + 1][to] = dp[i][j];
                    cnt[i + 1][to] = cnt[i][j];
                }
                else if (dp[i + 1][to] == dp[i][j]) {
                    cnt[i + 1][to] = (cnt[i + 1][to] + cnt[i][j]) % mod;
                }
            }
        }
    }

    //维护最小和方案数
    int mi = INF, ans = 0;
    for (int j = 0; j < m; j++) {
        if (dp[n][j] < mi) {
            mi = dp[n][j];
            ans = cnt[n][j];
        }
        else if (dp[n][j] == mi)
            ans = (ans + cnt[n][j]) % mod;
    }
    
    cout << mi << ' ' << ans << endl;
}

signed main() {
    cinios;
    int T = 1;
    cin >> T;
    for (int t = 1; t <= T; t++) {
        solve();
    }
    return 0;
}
/*
*/
//板子
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值