kmp的神奇之处

$ kmp $ 想必大家都不陌生,这里先贴个模板hh

从0开始:

 for (int i = 1, j = 0; i < s2.length(); i++)
    {
        while (j && s2[i] != s2[j])
            j = ne[j - 1];
        if (s2[i] == s2[j]) j++;
        ne[i] = j;
    }
    for (int i = 0, j = 0; i < s1.length(); i++)
    {
        while (j && s1[i] != s2[j]) j = ne[j - 1];
        if (s1[i] == s2[j]) j++;
        if (j == s2.length())
            cout << i - s2.length() + 2 << '\n', j = ne[j - 1];
    }

对于从1开始:

for (int i = 2, j = 0; i <= m; i++)
{
    while (j && s2[i] != s2[j + 1])
        j = ne[j];
    if (s2[i] == s2[j + 1])
        j++;
    ne[i] = j;
}
for (int i = 1, j = 0; i <= n; i++)
{
    while (j && s1[i] != s2[j + 1])
        j = ne[j];
    if (s1[i] == s2[j + 1])
        j++;
    if (j == m - 1)
    {
        cout << i - m + 2 << endl;
        j = ne[j];
    }
}

这两者本质上没有什么区别,只不过是位置不同,当然,这只是字符串匹配,但是你知道吗?他还能做到这样一种事:两个数组,任意一段连续的区间,\(kmp\) 都能做到判断一个数组是否在这一段区间上整体大于等于或者整体小于等于,做法也是类似于上面的匹配模式,只是改了一下判断条件

这是题目

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10, mod = 1e9 + 7;
signed main()
{
    std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int n; cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) cin >> a[i];
    vector<int> ne(2 * n + 1);
    vector<int> good(2 * n + 1);
    function<bool(int)> check;
    check = [&](int u) -> bool
    {
        if (2 * u - 1 > n) return false;
        for (int i = 1; i <= u; i++)
        {
            good[i] = i, good[2 * u - i] = i;
        }
        fill(ne.begin(), ne.end(), 0);
        for (int i = 2, j = 0; i <= 2 * u - 1; i++)
        {
            while (j && good[i] < good[j + 1]) j = ne[j];
            if (good[i] >= good[j + 1]) j++;
            ne[i] = j;
        }
        for (int i = 1, j = 0; i <= n; i++)
        {
            while (j && a[i] < good[j + 1]) j = ne[j];
            if (a[i] >= good[j + 1]) j++;
            if (j == 2 * u - 1) return true;
        }
        return false;
    };
    int l = 1, r = n;
    while (l < r)
    {
        int mid = (l + r + 1) >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    cout << l;
    return 0;
}

另外,这里还有一个关于字符串匹配的题:ABCABC

通过了解题意我们可以明确的知道这样一个性质,也就是操作之后字符串可以变成这样,设前缀为A,后缀为B,翻转之后为 \(inv\) 那么有:新字符串\(T=A+inv_B+inv_A+B -> A+inv_B=inv_A+B\),那么很明显的,只要是符合这种要求的子字符串A,B均可,换句话来说,我们将后半段翻转即:\(A+inv_B=inv_B+A\) 此时有后半段匹配其前缀,对于后缀B来说,将前半段翻转即可,然后统计一下符合后缀匹配前缀的下标,若某个下标都符合前缀和后缀的话,那么很明显的这个点就是中断点,分割即可
后缀匹配前缀的可以进行如下操作:

for(int i=2*n;i;i=ne[i-1]) if(i<=n) ok[i]++;

样例代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10, mod = 1e9 + 7;
signed main()
{
    std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int n; string t; cin >> n >> t;
    string s = t;
    reverse(s.begin() + n, s.end());
    vector<int> ok(2 * n + 1), ne(2 * n + 1);
    for (int i = 1, j = 0; i < 2 * n; i++)
    {
        while (j && s[i] != s[j]) j = ne[j - 1];
        if (s[i] == s[j]) j++;
        ne[i] = j;
    }
    for (int i = 2 * n; i; i = ne[i - 1])
        if (i <= n) ok[i]++;
    reverse(s.begin(), s.begin() + n);
    reverse(s.begin() + n, s.end());
    for (int i = 1, j = 0; i < 2 * n; i++)
    {
        while (j && s[i] != s[j]) j = ne[j - 1];
        if (s[i] == s[j]) j++;
        ne[i] = j;
    }
    for (int i = 2 * n; i; i = ne[i - 1])
        if (i <= n) ok[n - i]++;
    ok[0]++, ok[n]++;
    for (int i = 0; i <= n; i++)
    {
        if (ok[i] == 2)
        {
            return cout << t.substr(0, i) + t.substr(n + i) << '\n' << i,0;
        }
    }
    cout << -1;
    return 0;
}
posted @ 2024-05-26 12:05  o-Sakurajimamai-o  阅读(13)  评论(0编辑  收藏  举报
-- --