拓展KMP

exKMP

它其实是针对一个字符串,求出它的 Z 函数

用到了 manacher 的思想——重复利用已知信息

字符串下标从 $0$ 开始


定义

$z_i$:字符串 $s$ 从 $i$ 开始的后缀与 $s$ 的最大匹配长度

$mx$:当前匹配到的最远点,即 $\max\{z_i+i\}$

$id$:$mx$ 对应的 $i$,$mx=z_{id}+id$

默认 $mx=id=0$,$z_0$ 根据题目定义


算法流程

假设我们正在求 $z_i$,$z_i$ 初始为 $0$

那么如果 $mx\le i$,则只能暴力拓展 $i$

否则利用之前求的

如图,灰色部分相等,$s[0,z_{id}-1]=s[id,mx-1]$

前面两个红色部分也相等,由灰色相等可知 $s[0,z_{i-id}-1]=s[i-id,i-id+z_{i-id}-1]=s[i,\star]$

那么初始时 $z_i=\min\{z_{i-id},mx-i\}$

这就是 exKMP 的原理

如果红色箭头所指的边界 $\ge mx$,则超出部分无法判断,只能暴力拓展并更新 $mx,id$

复杂度分析同 manacher,$mx$ 单调不降,为 $O(|S|)$


代码:

P5410 【模板】扩展 KMP(Z 函数)

长得跟 manacher 也挺像

这里要处理 $b$ 与 $a$ 各个后缀的匹配长度,直接把 $a$ 接在 $b$ 后面求 $z$ 即可

#include<bits/stdc++.h>
using namespace std;

const int N = 40000010;
int z[N], n, id, lent, lens;
typedef long long ll;
ll ans, sum;
string s, t;
void exkmp(string str)
{
    n = str.length();
    for(int i = 1; i < n; ++i)
    {
        z[i] = (id + z[id] > i) ? min(z[i - id], id + z[id] - i) : 0;
        while(i + z[i] < n && str[z[i]] == str[i + z[i]])   ++z[i];
        if(i + z[i] > id + z[id])   id = i;
    }
}
int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> s >> t, lent = t.length(), lens = s.length();
    exkmp(t + s);
    sum = lent + 1;
    for(int i = 1; i < lent; ++i)   sum ^= 1ll * (i + 1) * (min(z[i], lent - i) + 1ll);
    for(int i = lent; i < lent + lens; ++i) ans ^= 1ll * (i - lent + 1) * (min(z[i], lent + lens - i) + 1);
    cout << sum << "\n" << ans; 
    return 0;
}

应用:

求前缀循环长度

P7114 [NOIP2020] 字符串匹配

求出 $S$ 的 Z 函数

从红线处匹配,红色块对应相等,又根据匹配与蓝色相等,蓝色又与绿色相等

所以对 $S$ 的每个前缀,都求出了循环次数:$\lfloor\frac{Z_{i+1}}{i+1}\rfloor+1$

这对应了 $(AB)^i$

然后关于出现奇数次字符个数的限制,可以预处理出前缀,后缀的个数

现在要快速求出对于 $i$ $A$ 有几种选择的总和,分析 exKMP 的性质

如果 $i$ 为偶数,则相当于原串某些字符出现次数减少偶数次,那在后缀中出现奇数次字符个数就是原串的

如果 $i$ 为奇数,同理可得是去掉所有 $(AB)^i$ 后的后缀 $+AB$ 的个数

于是用树状数组维护当前前缀中个数 $\le x$ 的位置数,求出两种情况

复杂度 $O(Tn\log 26)$

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll N = 1048600, SZ = (1ll << 14) + 5;
ll t, n, z[N], buc[28], qzh[N], ans, id, hzh[N], tree[28];
char s[N];
static char buf[SZ], *bgn = buf, *til = buf;
char getc()
{
    if(bgn == til)  bgn = buf, til = buf + fread(buf, 1, SZ, stdin);
    return bgn == til ? EOF : *bgn++;
}
ll read()
{
    char ch = getc();   ll x = 0;
    while(ch < '0' || ch > '9') ch = getc();
    while(ch >= '0' && ch <= '9')   x = (x << 1) + (x << 3) + ch - '0', ch = getc();
    return x; 
}
ll reads()
{
    char ch = getc();   ll x = 0;
    while(ch < 'a' || ch > 'z') ch = getc();
    while(ch >= 'a' && ch <= 'z')   s[x++] = ch, ch = getc();
    return x;
}
void print(ll x)
{
    if(x / 10)  print(x / 10);
    putchar(x % 10 + '0'); 
}
void clear()
{
    for(ll i = 0; i <= 26; ++i) buc[i] = 0;
    for(ll i = 0; i <= 27; ++i) tree[i] = 0;
    for(ll i = 0; i <= n; ++i)  z[i] = qzh[i] = hzh[i] = 0, s[i] = '\0';
    id = ans = 0;
}
void exkmp()
{
    for(ll i = 1; i < n; ++i)
    {
        z[i] = (z[id] + id > i) ? min(z[i - id], z[id] + id - i) : 0;
        while(i + z[i] <= n && s[z[i]] == s[i + z[i]])  ++z[i];
        if(i + z[i] > id + z[id])   id = i;
    }
}
void add(ll x, ll val)
{
    ++x;
    for(; x <= 27; x += x & (-x))   tree[x] += val;
}
ll query(ll x)
{
    ll res = 0; ++x;
    for(; x; x -= x & (-x)) res += tree[x];
    return res;
}
int main()
{
    t = read();
    while(t--)
    {
        clear();
        n = reads();
        for(ll i = 0, nw = 0; i < n; ++i)
        {
            if(buc[s[i] - 'a'] & 1) --nw;
            else    ++nw;
            ++buc[s[i] - 'a'], qzh[i] = nw; 
        }
        for(ll i = 0; i <= 26; ++i) buc[i] = 0;
        for(ll i = n - 1, nw = 0; i >= 0; --i)
        {
            if(buc[s[i] - 'a'] & 1) --nw;
            else    ++nw;
            ++buc[s[i] - 'a'], hzh[i] = nw;
        }
        add(qzh[0], 1), exkmp(), z[0] = n;
        for(ll i = 1; i < n - 1; ++i)
        {
            ll num = (z[i + 1] / (i + 1)) + 1;
            if(num * (i + 1) == n)  --num;
            ans += (num / 2) * query(hzh[0]) + (num - num / 2) * query(hzh[i + 1]);
            add(qzh[i], 1);
        }
        print(ans), putchar('\n');
    }
    return 0;
}
posted @ 2023-07-02 19:11  KellyWLJ  阅读(0)  评论(0编辑  收藏  举报  来源