• 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<<'
    ';
    			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
    ",top);
    	for(int i=1;i<=top;i++)
    	{
    		printf("%lld %lld
    ",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) 函数题再往上添吧

  • 相关阅读:
    CF1394A Boboniu Chats with Du 题解
    P3377 【模板】左偏树(可并堆)题解
    P2152 [SDOI2009]SuperGCD 题解
    在其他模块中调用代码
    教程:创建Go模块
    Go入门
    反悔贪心
    codeforces 1569 E. Playoff Restoration (meet-in-the-middle)
    codeforces 1036 F. Relatively Prime Powers (容斥+精度处理+大数边界处理)
    icpc沈阳2020 H. The Boomsday Project (dp+二分)
  • 原文地址:https://www.cnblogs.com/-Iris-/p/15350179.html
Copyright © 2020-2023  润新知