【KMP|DP】不含子串的最少删除次数

题目源于字节跳动2021秋招研发第二场第二题。

题意是说,给长度为n<10^5的字符串,里面包含十六进制数字(0~F),要求让这个字符串不能包含‘0010’这个子串,问最少要删除多少次。

其实这个子串很特殊,对于任意一个字符串,我总能找到一种方法使得我删除一个字符之后0010的个数减少一个。那么就是求一下出现了多少次0010即可。

但是如果给你的串不是0010而是一个随机的串呢?

又到了经典的“不包含子串”时间了。这种题一般是在KMP上dp。

先对模式串做一次KMP。

枚举原串的每一位,再枚举KMP的每个状态,若做匹配的时候没有匹配到结尾,说明不含模式串,此时可以直接对状态取最小,如果匹配到最后了则不能进行转移,因为此时表示含子串。

在原串i匹配到模式串j的情况下,如果我删除这一位,那么原串i+1则匹配模式串j,同时删除次数加一。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x;}
int ne[10], f[100005][10];
char str[100005];
char p[10] = " 0010";
int T;
int main()
{
    // time_t startt = clock();
    int m = 4;
    for(int i = 2, j = 0; i <= m; i++)
    {
        while(j && p[i] != p[j + 1]) j = ne[j];
        if(p[i] == p[j + 1]) j++;
        ne[i] = j;
    }
    scanf("%d", &T);
    while(T--)
    {
        scanf("%s", str + 1);
        int n = strlen(str + 1);

        for(int i = 0; i <= n; i++)
            for(int j = 0; j <= 4; j++) f[i][j] = 0x3f3f3f;
        f[0][0] = 0;
        for(int i = 0; i < n; i++)
        {
            for(int j = 0; j < m; j++)
            {
                int u = j;
                while(u && str[i + 1] != p[u + 1]) u = ne[u];
                if(str[i + 1] == p[u + 1]) u++;
                if(u < m) f[i + 1][u] = min(f[i + 1][u], f[i][j]);
                f[i + 1][j] = min(f[i + 1][j], f[i][j] + 1);
            }
        }
        int res = 0x3f3f3f;
        for(int i = 0; i < m; i++) res = min(res, f[n][i]);
        cout << res << endl;
    }
    // cerr << "~ #" << " done! time : " << (double)(clock()-startt) << " ms" << endl;
    // cerr << "~ #" << " done! time : " << (double)(clock()-startt)/CLOCKS_PER_SEC << " s" << endl;
}
  • 因为没有地方交题,所以不保证代码本身正确。

发表评论

电子邮件地址不会被公开。 必填项已用*标注