给定一个字符串 \(s[1 \cdots n]\)。
定义前缀函数 \(f_i\) 表示 \(s[1 \cdots i]\) 最长的相等的真前缀与真后缀的长度。
规定 \(f_1=0\)。
发现 \(f_i\) 至多为 \(f_{i-1}+1\)(匹配了 \(i\) 上的字符)。
考虑 \(i\) 上的字符不匹配的情况。
那么找到第二长的满足真前缀 \(=\) 真后缀的位置一定最优。
然后会发现这玩意儿其实就是前缀数组的定义。
直接不断跳前缀数组即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 3e6 + 10;
string s, t, g; int f[N];
int main() {
ios_base::sync_with_stdio(false); cin.tie(0), cout.tie(0);
cin >> s >> t; g = "$" + t + "#" + s;
int len = g.size() - 1, lent = t.size();
for (int i = 2; i <= len; ++i) {
int pos = i - 1;
while (pos && g[f[pos] + 1] ^ g[i]) pos = f[pos];
if (g[f[pos] + 1] == g[i]) f[i] = f[pos] + 1;
if (f[i] == lent) cout << i - 2 * lent << endl;
}
for (int i = 1; i <= lent; ++i) cout << f[i] << ' ';
return cout << endl, 0;
}
那么扩展 KMP 其实和这个类似。
定义 \(z_i\) 表示 \(s\) 和 \(s[i \cdots n]\) 的 LCP(最长公共前缀)长度。
实时维护一个区间 \([l,r]\) 表示当前能匹配的右端点最右的区间。
那么由定义有 \(s[1 \cdots r-l+1]=s[l \cdots r]\)。
- 如果当前 \(i \le r\)。
那么易得 \(s[i \cdots r]=s[i-l+1 \cdots r-l+1]\)。
根据 \(z_{i-l+1}\),得到 \(s\) 与 \(i-l+1\) 开头的后缀的 LCP 长度。
那么,若 \(z_{i-l+1} < r-i+1\),那么表示能匹配的长度至多为 \(z_{i-l+1}\)。
否则直接暴力向后匹配,更新 \(l,r\)。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 4e7 + 10;
string a, b, s; int l, r, resz, resp, z[N];
signed main() {
ios_base::sync_with_stdio(false); cin.tie(0), cout.tie(0);
cin >> a >> b; s = "#" + b + "$" + a;
int len = s.size() - 1, lena = a.size(), lenb = b.size();
z[1] = lenb, l = r = 1;
for (int i = 2; i <= len; ++i) {
if (i <= r && z[i - l + 1] < r - i + 1) z[i] = z[i - l + 1];
else {
z[i] = max(0ll, r - i + 1);
while (i + z[i] <= len && s[z[i] + 1] == s[i + z[i]]) ++z[i];
}
if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
}
for (int i = 1; i <= lenb; ++i) resz ^= (i * (z[i] + 1));
for (int i = 1; i <= lena; ++i) resp ^= (i * (z[i + lenb + 1] + 1));
return cout << resz << endl << resp << endl, 0;
}
标签:前缀,int,扩展,cdots,KMP,tie,size
From: https://www.cnblogs.com/MistZero/p/KMP.html