Z 函数学习笔记
声明,我不会 \(kmp\) 算法。。。
于是我直接硬莽一波 \(Z\) 函数
(默认字符串下标从 \(1\) 开始
设 \(z[i]\) 函数为 \(s[l,r]\) 与 \(s\) 的 \(lcp\)。
这里补充一下字符串的关键思想:一切复杂度高的操作都尽量使用之前处理过的转移函数,这样大部分情况复杂度会被均摊。
我们当前处理 \(z[r]\),我们仿照 \(manacher\) 的思想,随时维护一个 \(l\) 与 \(r\)。在 \([l,r]\) 这个区间内,我们可以依赖之前的 \(z[i-l+1]\) 来尽量减少我们扩展当前 \(z[i]\) 所需的复杂度。
所以,对于一个 \(i\) 来说
若 \(i \leq r\),则有(注意等于的时候也要暴力扩展
进一步分类讨论
若 \(z[i-l+1]<r-l+1\),则 \(z[i]=z[i-l+1]\)
否则,我们令 \(z[i]\) 从 \(z[i-l+1]\) 处开始暴力扩展
若 \(r < i\),那么我们暴力扩展 \(i\) 的 \(z\) 函数
这样我们就得到了一个 \(z\) 函数的 \(O(n)\)
复杂度分析
可以发现每次暴力扩展,\(r\) 都会随着暴力扩展改变,而 \(r\) 最多只能扩展到 \(n\),所以在均摊意义下,该算法达到 \(O(n)\)。
P5410 【模板】扩展 KMP(Z 函数)
第一个操作是 \(Z\) 函数基本操作,直接上板子。。
对于第二个操作,把两个串拼起来,中间用特殊字符相接即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ri register int
#define pb push_back
#define db double
#define pii pair<int,int>
#define ill long long
#define fi first
#define sc second
template<typename _T>
inline void read(_T &x)
{
x=0;char s=getchar();int f=1;
while(s<'0'||s>'9') {f=1;if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
x*=f;
}
const int np = 2e7 + 5;
char a[np];
char b[np * 2];
int z[np * 2];
int cas = 0;
inline void getz(char *c,int len)
{
int r(0),l(0);
int head(1);
z[1] = cas;
for(int i=2;i<=len;i++)
{
if(i < r)
{
if(z[i - l + 1] >= (r-i+1))
{
int pl = r;
int head = r-i+1;
while(c[head + 1] == c[pl + 1] && pl + 1 <= len) pl++,head++;
z[i] = pl-i+1;
l = i , r = i + z[i] - 1;
}
else z[i] = z[i - l + 1];
}
else
{
int pl = i-1;
head = 0;
while(c[head + 1] == c[pl + 1] && pl+1 <= len){pl++,head++;}
z[i] = head;
if(pl > r) r = pl , l = i;
}
}
int Ans =0 ;
int flag(0);
for(int i=1;i<=len;i++)
{
if(c[i] == '$')
{
cout<<Ans<<'\n';
flag =i;
break;
}
Ans ^= i * (z[i] + 1);
}
Ans = 0;
for(int i=flag + 1;i<=len;i++)
{
Ans ^= (i - flag) * (z[i] + 1);
}
cout<<Ans;
}
signed main()
{
scanf("%s",a + 1);
scanf("%s",b + 1);
int lena = strlen(a + 1);
int lenb = strlen(b + 1);
b[lenb + 1] = '$';
cas = lenb;
for(int i=1;i<=lena;i++) b[lenb + 1 + i] = a[i];
int len = strlen(b + 1);
getz(b,len);
}
CF432D Prefixes and Suffixes
求一遍 \(Z\) 函数,同时做出该串的 \(SAM\),提前预处理出所有子串的出现次数。
扫一遍字符串,一边扫一边在 \(SAM\) 上走,随时记录是否满足答案和该点出现次数
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ri register int
#define pb push_back
#define db double
#define pii pair<int,int>
#define ill long long
#define fi first
#define sc second
template<typename _T>
inline void read(_T &x)
{
x=0;char s=getchar();int f=1;
while(s<'0'||s>'9') {f=1;if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
x*=f;
}
const int np = 1e5 + 5;
char c[np * 2];
int len[np * 2],siz[np * 2],link[np * 2],cnt = 1,la = 1;
int son[29][np * 2],sa[np * 2],z[np * 2],bac[np * 2],Ans;
int a1[np * 2],a2[np * 2],top;
inline void add(int x)
{
int p,q,now,k,ch=1;
p = la,now = la = ++cnt,len[now] = len[p] + (siz[now] = 1);
for(;p && !son[x][p];son[x][p] = now,p=link[p]);
if(!p) return (void)(link[now] = 1);
if(len[p] + 1==len[q = son[x][p]]) return (void)(link[now] = q);
len[k = ++cnt] = len[p] + 1,link[k] = link[q] , link[q] = link[now] = k;
for(;ch<=26;ch++) son[ch][k] = son[ch][q];
for(;p && !(son[x][p]^q);son[x][p]=k,p=link[p]);
}
inline void getz(char *c,int len)
{
int l(0),r(0),head(0),pl(0);
z[1] = len;
for(int i=2;i<=len;i++)
{
if(i < r)
{
if(z[i-l+1] >= (r-i+1))
{
pl = r;
head = r-i+1;
while(c[head+1]==c[pl + 1] && pl + 1<=len) pl++,head++;
z[i] = head,r = pl,l=i;
}
else z[i] = z[i-l+1];
}
else
{
head = 0;
pl = i-1;
while(c[head + 1]==c[pl + 1] && pl + 1<=len) pl++,head++;
z[i] = head,r = pl,l = i;
}
}
}
inline void solve(int le)
{
int Now = 1;
int plen = 0;
for(int i=le;i>=1 && c[i] != '$';i--)
{
plen++;
Now = son[c[plen]-'A'+1][Now];
if(z[i] == plen) a1[++top] = plen,a2[top] = siz[Now];
}
printf("%lld\n",top);
for(int i=1;i<=top;i++)
{
printf("%lld %lld\n",a1[i],a2[i]);
}
}
signed main()
{
scanf("%s",c+1);
int le = strlen(c + 1);
for(int i=1;i<=le;i++) add(c[i]-'A'+1);
for(int i=1;i<=cnt;i++) bac[len[i]]++;
for(int i=1;i<=cnt;i++) bac[i] += bac[i-1];
for(int i=1;i<=cnt;i++) sa[bac[len[i]]--] = i;
for(int i=cnt,x;i>=1;i--)
{
x = sa[i];
siz[link[x]] += siz[x];
}
c[le + 1] = '$';
for(int i=1;i<=le;i++) c[le + 1 + i] = c[i];
le = strlen(c + 1);
getz(c,le);
solve(le);
}
感觉这个算法的关键使用是如何用好后缀和整体的 \(\operatorname{lcp}\)。
其实这个算法本身并不是特别难,既没有 \(SAM\) 那种晦涩的思想,也没有 \(SA\) 频出的骚操作……
(好吧只是因为我太菜了就只配做做板子了
以后发现比较有趣的 \(Z\) 函数题再往上添吧