作用
- 字符串 S [ 0... n − 1 ] S[0...n-1] S[0...n−1]的 Z [ i ] Z[i] Z[i]数组, Z [ i ] Z[i] Z[i]表示 S [ 0... n − 1 ] S[0...n-1] S[0...n−1]和 S [ i . . . n − 1 ] S[i...n-1] S[i...n−1]的最长公共前缀 ( L C P ) (LCP) (LCP)
原理
运用自动机的思想寻找限制条件下的状态转移函数,使得可以借助之前的状态加速计算新的状态
—摘自oiwiki
- 类似 k m p , m a n a c h e r kmp,manacher kmp,manacher等字符串算法,我们从前往后递推求解,不重复计算,利用之前计算过的来得到新的值,使得将常规做法 O ( n 2 ) O(n^2) O(n2)的时间复杂度优化到 O ( n ) O(n) O(n)
约定字符串从 0 0 0开始,我们维护一个 [ l , r ] [l,r] [l,r]的区间,称为 Z − b o x Z-box Z−box,这个区间表示当前计算到的右端点最远的 L C P LCP LCP,此区间内部的 Z Z Z值我们几乎都可以 O ( 1 ) O(1) O(1)得到,因为根据 Z − b o x Z-box Z−box定义,如果 i i i在这个范围内且 L C P LCP LCP长度合理 ( Z [ i − l ] ≤ r − i + 1 ) (Z[i-l]\leq r-i+1) (Z[i−l]≤r−i+1),有 Z [ i ] = Z [ i − l ] Z[i]=Z[i-l] Z[i]=Z[i−l],如果长度不合理,则更新为 r − i + 1 r-i+1 r−i+1,之后暴力拓展;然后更新 Z − b o x Z-box Z−box即可,代码如下
void get_z(string &s){
int len = s.length();
Z[0] = len;
for(int i=1,l=0,r=0;i<len;i++){
if(i <= r) Z[i] = min(Z[i - l], r - i + 1);
while(s[Z[i]] == s[i + Z[i]]) Z[i] += 1;
if(i + Z[i] - 1 > r){
l = i;
r = i + Z[i] - 1;
}
}
}
例题
https://www.luogu.com.cn/problem/P5410
- 求字符串 b b b的 Z Z Z数组和 b b b和字符串 a a a的每一个后缀的 L C P LCP LCP
- 我们做一个字符串 c = b + a c=b+a c=b+a,然后计算字符串 c c c的 Z Z Z数组,然后根据性质易得
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 4e7 + 100;
int Z[N];
string s;
int get_Z(int len){
Z[0] = len;
for(int i=1,l=0,r=0;i<len;i++){
if(i <= r) Z[i] = min(Z[i - l], r - i + 1);
while(s[Z[i]] == s[i + Z[i]]) Z[i] += 1;
if(i + Z[i] - 1 > r){
l = i;
r = i + Z[i] - 1;
}
}
};
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
string a, b;
cin >> a >> b;
int a_len = a.length();
int b_len = b.length();
s = b + a;
get_Z((int)s.length());
ll ans = 0;
for(int i=0;i<b_len;i++){
ans ^= 1ll * (i + 1) * (1 + min(1ll * Z[i], 1ll * b_len - i));
}
cout << ans << '\n';
ans = 0;
for(int i=0;i<a_len;i++){
ans ^= 1ll * (i + 1) * (1 + min(1ll * Z[i + b_len], 1ll * a_len));
}
cout << ans;
return 0;
}