zoukankan      html  css  js  c++  java
  • 失配树

    名字看起来挺高级的,然而其实就是 ( ext{KMP}) 上树啦。

    我们将每个点的 (nex[i])(i) 连边,那么最终 (border) 关系会形成一棵树,之后就可以在树上搞事情啦!

    P5829 【模板】失配树

    这题比较裸,直接根据定义建树之后对于两个前缀求出在 (fail) 树上的最近公共祖先即可。

    $ exttt{code}$
    // Author:A weak man named EricQian
    #include<bits/stdc++.h>
    using namespace std;
    #define infll 0x7f7f7f7f7f7f7f7f
    #define inf 0x3f3f3f3f
    #define Maxn 1000005
    #define Maxpown 22
    typedef long long ll;
    inline int rd()
    {
    	 int x=0;
    	 char ch,t=0;
    	 while(!isdigit(ch = getchar())) t|=ch=='-';
    	 while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
    	 return x=t?-x:x;
    }
    int n,q,tot;
    int pre[Maxn],dep[Maxn];
    int fa[Maxn][Maxpown];
    char s[Maxn];
    inline int query(int x,int y)
    {
    	 if(dep[x]>dep[y]) swap(x,y);
    	 for(int i=20;i>=0;i--) if(dep[fa[y][i]]>=dep[x]) y=fa[y][i];
    	 if(x==y) return fa[x][0];
    	 for(int i=20;i>=0;i--) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    	 return fa[x][0];
    }
    int main()
    {
    	 //ios::sync_with_stdio(false); cin.tie(0);
    	 //freopen(".in","r",stdin);
    	 //freopen(".out","w",stdout);
    	 scanf("%s",s+1),n=strlen(s+1);
    	 for(int i=2,j=0;i<=n;i++)
    	 {
    	 	 while(j && s[i]!=s[j+1]) j=pre[j];
    	 	 if(s[i]==s[j+1]) j++;
    	 	 pre[i]=j,fa[i][0]=j,dep[i]=dep[j]+1;
    	 }
    	 for(int i=1;i<=20;i++)
    	 	 for(int j=1;j<=n;j++)
    	 	 	 fa[j][i]=fa[fa[j][i-1]][i-1];
    	 q=rd();
    	 for(int i=1,x,y;i<=q;i++) x=rd(),y=rd(),printf("%d
    ",query(x,y));
    	 //fclose(stdin);
    	 //fclose(stdout);
    	 return 0;
    }
    

    小插曲:(小声)

    ZCETHAN 告诉 EricQian 这一题用失配树做,EricQian 立刻表示它不会失配树。【过了 5 秒】EricQian 大声喊道:我发明了失配树!

    CF432D Prefixes and Suffixes

    题意:给你一个长度为 (n) 的长字符串,“完美子串”既是它的前缀也是它的后缀,求“完美子串”的个数且统计这些子串的在长字符串中出现的次数。

    我们发现对于一个前缀的出现个数其实就是:(难以表述直接用伪代码列出了)

    len=这个前缀的长度 
    for(int i=1;i<=n;i++)
    	 for(int j=i;j;j=nex[j])
    	 	 ans+=(j==len)?1:0;
    

    之后我们发现对于同一个前缀它可能被访问多次,这个可以直接倒换循环顺序实现 (O(n)) 解决。(于是你就发明的失配树)

    $ exttt{code}$
    // Author:A weak man named EricQian
    #include<bits/stdc++.h>
    using namespace std;
    #define infll 0x7f7f7f7f7f7f7f7f
    #define inf 0x3f3f3f3f
    #define Maxn 100005
    typedef long long ll;
    inline int rd()
    {
    	 int x=0;
    	 char ch,t=0;
    	 while(!isdigit(ch = getchar())) t|=ch=='-';
    	 while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
    	 return x=t?-x:x;
    }
    inline ll maxll(ll x,ll y){ return x>y?x:y; }
    inline ll minll(ll x,ll y){ return x<y?x:y; }
    inline ll absll(ll x){ return x>0ll?x:-x; }
    inline ll gcd(ll x,ll y){ return (y==0)?x:gcd(y,x%y); }
    int n,tot;
    int nex[Maxn],cnt[Maxn];
    int ans[Maxn][2];
    char s[Maxn];
    int main()
    {
    	 //ios::sync_with_stdio(false); cin.tie(0);
    	 //freopen(".in","r",stdin);
    	 //freopen(".out","w",stdout);
    	 scanf("%s",s+1),n=strlen(s+1);
    	 for(int i=2,j=0;i<=n;i++)
    	 {
    	 	 while(j && s[i]!=s[j+1]) j=nex[j];
    	 	 if(s[i]==s[j+1]) j++;
    	 	 nex[i]=j;
    	 }
    	 for(int i=n;i>=1;i--) cnt[i]++,cnt[nex[i]]+=cnt[i];
    //	 for(int i=1;i<=n;i++)
    //	 	 for(int j=i;j;j=nex[j]) cnt[j]++;
    	 ans[++tot][0]=n,ans[tot][1]=1;
    	 for(int i=nex[n];i;i=nex[i])
    	 	 ans[++tot][0]=i,ans[tot][1]=cnt[i];
    	 printf("%d
    ",tot);
    	 for(int i=tot;i>=1;i--) printf("%d %d
    ",ans[i][0],ans[i][1]);
    	 //fclose(stdin);
    	 //fclose(stdout);
    	 return 0;
    }
    

    P2375 [NOI2014] 动物园

    这道题可以根本不用往失配树去理解,我们发现每一次最长合法 ( ext{border}) 的长度最多只会增加 (1),那么直接暴力跑 ( ext{KMP}) 并及时处理 ( ext{border}) 长度大于一半的情况即可。

    一些正确性证明:如果这个位置的 ( ext{border}) 长度大于了 (frac{i}{2}),这个 ( ext{border}) 的后缀起始点将永远不再会成为答案,因此跳出这个 ( ext{border}) 一定是对的。

    $ exttt{code}$
    // Author:A weak man named EricQian
    #include<bits/stdc++.h>
    using namespace std;
    #define infll 0x7f7f7f7f7f7f7f7f
    #define inf 0x3f3f3f3f
    #define Maxn 1000005
    #define mod 1000000007
    typedef long long ll;
    inline int rd()
    {
    	 int x=0;
    	 char ch,t=0;
    	 while(!isdigit(ch = getchar())) t|=ch=='-';
    	 while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
    	 return x=t?-x:x;
    }
    inline ll maxll(ll x,ll y){ return x>y?x:y; }
    inline ll minll(ll x,ll y){ return x<y?x:y; }
    inline ll absll(ll x){ return x>0ll?x:-x; }
    inline ll gcd(ll x,ll y){ return (y==0)?x:gcd(y,x%y); }
    int n,ans;
    char s[Maxn];
    int nex[Maxn],dep[Maxn];
    int main()
    {
    	 //ios::sync_with_stdio(false); cin.tie(0);
    	 //freopen(".in","r",stdin);
    	 //freopen(".out","w",stdout);
    	 int T=rd();
    	 while(T--)
    	 {
    	 	 scanf("%s",s+1),n=strlen(s+1),ans=1;
    	 	 dep[1]=1;
    	 	 for(int i=2,j=0;i<=n;i++)
    	 	 {
    	 	 	 while(j && s[i]!=s[j+1]) j=nex[j];
    	 	 	 if(s[i]==s[j+1]) j++;
    	 	 	 nex[i]=j,dep[i]=dep[j]+1;
    		 }
    		 for(int i=2,j=0;i<=n;i++)
    		 {
    		 	 while(j && s[i]!=s[j+1]) j=nex[j];
    		 	 if(s[i]==s[j+1]) j++;
    		 	 while(j*2>i) j=nex[j];
    		 	 ans=1ll*ans*(1ll*dep[j]+1ll)%mod;
    		 }
    		 printf("%d
    ",ans);
    	 }
    	 //fclose(stdin);
    	 //fclose(stdout);
    	 return 0;
    }
    

    P3426 [POI2005]SZA-Template

    考虑设 (dp(i)) 表示主串的前缀 ([1,i]) 需要的最短长度。

    容易发现在失配树上 (dp(i)) 沿着树成单调递增关系,考虑 (dp(i))(dp(nex_i)) 的关系。

    如果 (dp(i)in (dp(nex_i),nex_i]),由于 ([1,i]) 的一个 border 为 (dp(nex_i)),所以 (dp(nex_i)) 也一定能够生成这个串。

    所以 (dp(i)) 能够生成的串一定是 (dp(nex_i)) 能够生成的真子集,即答案取 (dp(nex_i)) 一定比 ((dp(nex_i),nex_i]) 优。

    因此我们只用判断能否由 (dp(nex_i)) 生成 ([1,i]) 即可,具体即记录 (dp(nex_i)) 能够生成的最长串 (s),如果 (|s|+|dp(nex_i)|ge i) 即可。

    如果上述情况不成立,意味着 (dp(i)=i)

    $ exttt{code}$
    // Author:A weak man named EricQian
    #include<bits/stdc++.h>
    using namespace std;
    #define infll 0x7f7f7f7f7f7f7f7f
    #define inf 0x3f3f3f3f
    #define Maxn 500005
    typedef long long ll;
    inline int rd()
    {
    	 int x=0;
    	 char ch,t=0;
    	 while(!isdigit(ch = getchar())) t|=ch=='-';
    	 while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
    	 return x=t?-x:x;
    }
    inline ll maxll(ll x,ll y){ return x>y?x:y; }
    inline ll minll(ll x,ll y){ return x<y?x:y; }
    inline ll absll(ll x){ return x>0ll?x:-x; }
    inline ll gcd(ll x,ll y){ return (y==0)?x:gcd(y,x%y); }
    int n;
    int nex[Maxn],maxx[Maxn],dp[Maxn];
    char s[Maxn];
    int main()
    {
    	 //ios::sync_with_stdio(false); cin.tie(0);
    	 //freopen(".in","r",stdin);
    	 //freopen(".out","w",stdout);
    	 scanf("%s",s+1),n=strlen(s+1);
    	 for(int i=2,j=0;i<=n;i++)
    	 {
    	 	 while(j && s[i]!=s[j+1]) j=nex[j];
    	 	 if(s[i]==s[j+1]) j++;
    	 	 nex[i]=j;
    	 }
    	 for(int i=1;i<=n;i++)
    	 {
    	 	 if(maxx[dp[nex[i]]]+dp[nex[i]]>=i) dp[i]=dp[nex[i]];
    	 	 else dp[i]=i;
    	 	 maxx[dp[i]]=i;
    	 }
    	 printf("%d
    ",dp[n]);
    	 //fclose(stdin);
    	 //fclose(stdout);
    	 return 0;
    }
    
  • 相关阅读:
    CLR线程池基础
    何时使用线程
    【转】PowerShell入门(十一):编写脚本模块
    【转】PowerShell入门(十):使用配置文件
    【转】PowerShell入门(九):访问.Net程序集、COM和WMI
    【转】PowerShell入门(八):函数、脚本、作用域
    【转】PowerShell入门(七):管道——在命令行上编程
    【转】PowerShell入门(六):远程操作
    【转】PowerShell入门(五):Cmd命令与PowerShell命令的交互
    【转】PowerShell入门(四):如何高效地使用交互式运行环境?
  • 原文地址:https://www.cnblogs.com/EricQian/p/15482551.html
Copyright © 2011-2022 走看看