1 条题解

  • 0
    @ 2025-8-24 22:14:44

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar WYXkk
    Zzz Zzz

    搬运于2025-08-24 22:14:44,当前版本为作者最后更新于2019-12-19 09:35:29,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    P5829 【模板】失配树 题解

    题意简述

    给你一个串 SS,定义一个字符串的 border\text{border} 为它的非本身的既是它的前缀又是它的后缀的字符串,mm 次询问,每次询问给定两个数 i,ji,j,询问 S1...iS_{1...i}S1...jS_{1...j} 的最长公共 border\text{border}

    1S106,1i,jS,1m1051\le|S|\le10^6,1\le i,j\le|S|,1\le m\le10^5,字符集为 2626 个小写字母。

    解法

    首先我们来看另外一个与它相关的问题:如何求出一个字符串的所有 border\text{border}

    如果一个字符串既是 SS 的前缀又是 SS 的后缀,那么我们把 SS 自己平移一下就可以前后重合,然后我们就可以继续匹。。。。。诶?这不是 KMP\text{KMP} 么?

    所以我们对原串 KMP\text{KMP} 一遍,然后就可以发现,S1next(S)S_{1\cdots next(|S|)}SSnext(S)+1SS_{|S|-next(|S|)+1\cdots |S|} 是完全一样的!于是 S=S1next(S)S^\prime=S_{1\cdots next(|S|)} 就是 SS 的一个 border\text{border}

    那么 SS 是否还有其他 border\text{border} 呢?有。根据上文,S=S1next(next(S))S^{\prime\prime}= S_{1\cdots next(next(|S|))}S=S1next(S)S^\prime=S_{1\cdots next(|S|)} 的一个 border\text{border},于是 SS^{\prime\prime} 既是 SS^\prime 的前缀又是 SS^\prime 的后缀。由于 SS^\prime 既是 SS 的后缀又是 SS 的前缀,所以 SS^{\prime\prime} 既是 SS 的前缀的前缀又是 SS 的后缀的后缀,所以 SS^{\prime\prime} 既是 SS 的前缀又是 SS 的后缀,也是 SSborder\text{border}

    以此类推,我们可以得到,SS 的所有 border\text{border} 为:$S_{1\cdots next(|S|)},S_{1\cdots next(next(|S|))},S_{1\cdots next(next(next(|S|)))},\cdots$。

    我们再来看看原题:求两个前缀的最长公共 border\text{border}

    老套路,先对原串 KMP\text{KMP} 一遍,于是我们可以通过跳两个前缀的 nextnext 求到两个前缀的所有 border\text{border}

    等等,这不是暴力求 LCA\text{LCA} 的思路吗?先暴力找到所有祖先,再判断相交。

    所以我们可以通过 nextnext 数组建一棵树出来(可以发现这就是只有一个字符串的 AC\text{AC} 自动机的 failfail 树,所以我们也叫它 failfail 树),容易发现两个前缀的最长公共 border\text{border} 就是它们在 failfail 树上的 LCA\text{LCA}(当它们是祖先—后代关系时除外,此时结果是祖先的父亲)。

    综上所述,本题的做法如下:

    • 对原串 KMP\text{KMP} 一遍,求出 nextnext 数组;
    • 构建 failfail 树;
    • failfail 树上跑 LCA\text{LCA}(倍增和 tarjan\text{tarjan} 都可以)。

    代码如下:

    #include<cstdio>
    #include<iostream>
    #include<fstream>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    #define Set(a) memset(a,0,sizeof(a))
    #define F(i,a,b) for(register int i=a,i##end=b;i<=i##end;++i)
    #define UF(i,a,b) for(register int i=a,i##end=b;i>=i##end;--i)
    #define openf(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
    #define re register
    #define ri re int
    #define il inline
    typedef long long ll;
    typedef unsigned long long ull;
    template<typename T> inline T rd(T& x)
    {
    	T f=1;x=0;char c=getchar();
    	for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    	for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(T)(c-'0');
    	x*=f;
    	return x;
    }
    ll rd(){ll x;rd(x);return x;}
    inline int max(int a,int b){return a>b?a:b;}
    inline int min(int a,int b){return a<b?a:b;}
    const int inf=1<<30;
    
    const int N=1000005;
    int fa[N][22],n,m,dep[N];char s[N];
    int main()
    {
    	scanf("%s",s+1);n=strlen(s+1);
    	fa[0][0]=fa[1][0]=0,dep[0]=0,dep[1]=1;
    	for(ri i=2,j=0;i<=n;++i)
    	{
    		while(j!=0&&s[j+1]!=s[i]) j=fa[j][0];
    		if(s[j+1]==s[i]) ++j;
    		fa[i][0]=j,dep[i]=dep[j]+1;
    	}
    	//以上为kmp
    	//以下为倍增lca
    	F(i,1,21) F(j,1,n) fa[j][i]=fa[fa[j][i-1]][i-1];
    	rd(m);
    	while(m--)
    	{
    		int x,y;rd(x);rd(y);if(dep[x]<dep[y]) swap(x,y);
    		UF(i,21,0) if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
    		UF(i,21,0) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    		printf("%d\n",fa[x][0]);
    	}
    	return 0;
    }
    

    突然感觉这题有种强行二合一的感觉(


    倍增不开 O2-\text O2 太慢了,有几个点会 900ms900ms。。。这里再贴个 tarjan\text{tarjan}

    #include<cstdio>
    #include<iostream>
    #include<fstream>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    #define Set(a) memset(a,0,sizeof(a))
    #define F(i,a,b) for(register int i=a,i##end=b;i<=i##end;++i)
    #define UF(i,a,b) for(register int i=a,i##end=b;i>=i##end;--i)
    #define openf(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
    #define re register
    #define ri re int
    #define il inline
    typedef long long ll;
    typedef unsigned long long ull;
    template<typename T> inline T rd(T& x)
    {
    	T f=1;x=0;char c=getchar();
    	for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    	for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(T)(c-'0');
    	x*=f;
    	return x;
    }
    ll rd(){ll x;rd(x);return x;}
    inline int max(int a,int b){return a>b?a:b;}
    inline int min(int a,int b){return a<b?a:b;}
    const int inf=1<<30;
    
    const int N=1000005;
    int next[N],n,m;char s[N];
    int fa[N];int get(int x){return fa[x]==x?x:fa[x]=get(fa[x]);}void merge(int x,int y){if((x=get(x))!=(y=get(y)))fa[x]=y;}
    bool vis[N];
    int head[N],to[2*N],nxt[2*N],tot;void add(int u,int v){to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}//graph
    int head2[N],to2[2*N],nxt2[2*N],num[2*N],tot2;void add2(int u,int v,int w){to2[++tot2]=v;num[tot2]=w;nxt2[tot2]=head2[u];head2[u]=tot2;}//query
    int ans[N],x[N],y[N];
    void tarjan(int x)
    {
    	vis[x]=true;
    	for(ri i=head[x];i;i=nxt[i]) if(!vis[to[i]]) {tarjan(to[i]);merge(to[i],x);}
    	for(ri i=head2[x];i;i=nxt2[i]) if(vis[to2[i]]) ans[num[i]]=get(to2[i]);
    }
    int main()
    {
    	scanf("%s",s+1);n=strlen(s+1);
    	next[0]=next[1]=0;F(i,1,n) fa[i]=i;
    	for(ri i=2,j=0;i<=n;++i)
    	{
    		while(j!=0&&s[j+1]!=s[i]) j=next[j];
    		if(s[j+1]==s[i]) ++j;
    		next[i]=j;
    	}
    	F(i,1,n) add(next[i],i);
    	rd(m);
    	F(i,1,m){rd(x[i]);rd(y[i]);add2(x[i],y[i],i);add2(y[i],x[i],i);}
    	tarjan(0);
    	F(i,1,m) printf("%d\n",(ans[i]==x[i]||ans[i]==y[i])?next[ans[i]]:ans[i]);
    	return 0;
    }
    
    • 1

    信息

    ID
    4833
    时间
    1000ms
    内存
    512MiB
    难度
    5
    标签
    递交数
    0
    已通过
    0
    上传者