zoukankan      html  css  js  c++  java
  • 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) 函数题再往上添吧

  • 相关阅读:
    不务正业系列-浅谈《过气堡垒》,一个RTS玩家的视角
    [LeetCode] 54. Spiral Matrix
    [LeetCode] 40. Combination Sum II
    138. Copy List with Random Pointer
    310. Minimum Height Trees
    4. Median of Two Sorted Arrays
    153. Find Minimum in Rotated Sorted Array
    33. Search in Rotated Sorted Array
    35. Search Insert Position
    278. First Bad Version
  • 原文地址:https://www.cnblogs.com/-Iris-/p/15350179.html
Copyright © 2011-2022 走看看