拓展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|)$
代码:
长得跟 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;
}
应用:
求前缀循环长度
求出 $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;
}