zoukankan      html  css  js  c++  java
  • 「后缀自动机」

    前言

    这比后缀数组难啊。

    但似乎其实我并不觉得比sa好用。

    很难懂,本来看了一天的证明现在屁都没剩,事实证明打板子才是对的。

    应用

    很多,但我都不会。

    • 求第K大
    • 本质不同的子串
    • 求排名
    • 多个串求最长公共串
    • 其实还有很多神仙操作...

    所以我为什么要写总结啊喂。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=5000;
    int n,lst,cnt,len[N],buc[N],ch[N][26],fa[N],mx[N],mn[N],rk[N];
    char s[N];
    inline void extend(int c){
        int p=lst,np;np=lst=++cnt;
        len[np]=len[p]+1;
        for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
        if(!p) fa[np]=1;
        else{
            int q=ch[p][c];
            if(len[q]==len[p]+1) fa[np]=q;
            else{
                int nq=++cnt;
                fa[nq]=fa[q];fa[q]=fa[np]=nq;
                len[nq]=len[p]+1;
                for(int i=0;i<26;++i) ch[nq][i]=ch[q][i];
                for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
            }
        }
    }
    int main(){
        memset(mn,0x3f,sizeof mn);
        lst=cnt=1;
        scanf("%d%s",&n,s+1);
        for(int i=1;s[i];++i) extend(s[i]-'a');
    
        int DD=strlen(s+1);
    
        for(int i=1;i<=cnt;++i) buc[len[i]]++;
        for(int i=0;i<=DD;++i) buc[i]+=buc[i-1];
        for(int i=1;i<=cnt;++i) rk[buc[len[i]]--]=i;
    
        for(int i=2;i<=n;++i){
            scanf("%s",s+1);
            int l=strlen(s+1);
            int LCS=0,root=1;
            for(int j=1;j<=l;++j){
                if(ch[root][s[j]-'a']){
                    root=ch[root][s[j]-'a'];
                    mx[root]=max(mx[root],++LCS);
                }
                else{
                    while(root&&!ch[root][s[j]-'a']) root=fa[root];
                    if(!root)root=1,LCS=0;
                    else{
                        LCS=min(LCS,len[root]);
                        root=ch[root][s[j]-'a'];
                        mx[root]=max(mx[root],++LCS);
                    }
                }
    //            printf("%d %d %d %d %d
    ",i,j,LCS,root,ch[root][s[j+1]-'a']);
            }
            for(int i=cnt;i;--i){
                mn[rk[i]]=min(mn[rk[i]],mx[rk[i]]);
                if(fa[rk[i]]) mx[fa[rk[i]]]=min(len[fa[rk[i]]],max(mx[fa[rk[i]]],mx[rk[i]]));
                mx[rk[i]]=0;
            }
    
        }
        int ans=0;
        for(int i=1;i<=cnt;++i) ans=max(ans,mn[rk[i]]);
        printf("%d
    ",ans);
        return 0;
    }
    公共串
    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int N=1e6+50;
    int n,point_cnt,lst;
    int len[N],ch[N][26],fa[N],endpos[N],tra[N];
    char s[N];
    vector <int> v[N];
    inline void extend(int c){
        int p=lst,np;np=lst=++point_cnt;
        endpos[np]=1;
        v[len[np]=len[p]+1].push_back(np);
        for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
        if(!p) fa[np]=1;
        else{
            int q=ch[p][c];
            if(len[q]==len[p]+1) fa[np]=q;
            else{
                int nq=++point_cnt;
                fa[nq]=fa[q];fa[np]=fa[q]=nq;
                v[len[nq]=len[p]+1].push_back(nq);
                for(int i=0;i<26;++i) ch[nq][i]=ch[q][i];
                for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
            }
        }
    }
    #define xx endpos[v[i][j]]*(endpos[v[i][j]]-1)/2
    int ans;
    signed main(){
        lst=point_cnt=1;
        scanf("%s",s+1);
        n=strlen(s+1);
        reverse(s+1,s+n+1);
        for(int i=1;i<=n;++i) extend(s[i]-'a');
        ans=(1+n)*n/2*(n-1);
        for(int i=n;i;--i) for(int j=0;j<(int)v[i].size();++j) endpos[fa[v[i][j]]]+=endpos[v[i][j]];
        for(int i=n;i;--i) for(int j=0;j<(int)v[i].size();++j) tra[v[i][j]]+=xx,tra[fa[v[i][j]]]-=xx;
        for(int i=n;i;--i) for(int j=0;j<(int)v[i].size();++j) ans-=2*len[v[i][j]]*tra[v[i][j]];
        printf("%lld
    ",ans);
        return 0;
    }
    差异

    例题

    A. 弦论

    对于一个给定长度为N的字符串,求它的第K小子串是什么。分两种情况:不同位置的相同子串算1个/多个。

    $sam$跑出来,对$DAG$跑拓扑,可以通过对长度$len$排序求出拓扑序,倒着更新$f[i]$和$g[i]$表示$DAG$上的子串数量。

    转移$f[i]=(sum f[j])+1,g[i]=(sum g[j])+endpos[i]$,其中$endpos[]$表示这个位置表示的字符串在串中的结尾位置,其实也就是在串中的出现次数。

    $endpos$也需要转移,只不过因为它关乎$parent tree$,所以它要在树上转移,转移也很好转移,把枚举$ch$变成把它的贡献给$fa$就对了。

    此位置卡住了我,因为我开始学的时候并不会按长度排序,只是正常建边然后找出拓扑序再更新$endpos$,但这样求出来是错的$endpos$。

    因为对于$parent tree$上的父子关系,在$sam$上并不具有明确的拓扑关系,因为在$sam$上我的$fa$并不一定和我连边了。

    我们之所以按照长度排序,正是省去对$sam,parent tree$两个结构都考虑的麻烦。因为在$sam,parent tree$上的遍历情况都是满足长度递增的。

    在求第K小的时候,按照在$sam$上的$ch$字典序做类似主席树的操作。

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int N=2e6+50;
    int T,K,B,lst,point_cnt,fa[N],ch[N][26],len[N],f[N],g[N],endpos[N],head[N],to[N<<1],nxt[N<<1],deg[N],vis[N],sta[N],fuc[N];
    char s[N];
    inline void lnk(int x,int y){
        if(!x||!y) return;
        to[++B]=y,nxt[B]=head[x],head[x]=B,deg[y]++;
    }
    bool cmp(int a,int b){return len[a]>len[b];}
    inline void extend(int c){
        int p=lst,np;np=lst=++point_cnt;
        len[np]=len[p]+1;
        endpos[np]=1;
        for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
        if(!p) fa[np]=1;
        else{
            int q=ch[p][c];
            if(len[q]==len[p]+1) fa[np]=q;
            else{
                int nq=++point_cnt;
                fa[nq]=fa[q];
                len[nq]=len[p]+1;
                for(int i=0;i<26;++i) ch[nq][i]=ch[q][i];
                fa[q]=fa[np]=nq;
                for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
            }
        }
    }
    void dfs1(int x){
        vis[x]=1;
        for(int i=0;i<26;++i) if(ch[x][i]){
            lnk(ch[x][i],x);
            if(!vis[ch[x][i]]) dfs1(ch[x][i]);
        }
    }
    signed main(){
        lst=point_cnt=1;
        scanf("%s%lld%lld",s+1,&T,&K);
        for(int i=1;s[i];++i) extend(s[i]-'a');
        dfs1(1);
        for(int i=1;i<=point_cnt;++i) fuc[i]=i;
        sort(fuc+1,fuc+point_cnt+1,cmp);
    
        for(int i=1;i<=point_cnt;++i) endpos[fa[fuc[i]]]+=endpos[fuc[i]];
        
        for(int i=1;i<=point_cnt;++i) if(!deg[i]) sta[++sta[0]]=i;
        for(int i=1;i<=sta[0];++i){
            int x=sta[i];
            f[x]+=endpos[x];g[x]++;
            for(int i=head[x];i;i=nxt[i]){
                f[to[i]]+=f[x];g[to[i]]+=g[x];
                if(--deg[to[i]]==0) sta[++sta[0]]=to[i];
            }
        }
        if((T==0&&g[1]<K)||(T==1&&f[1]<K)) return !puts("-1");
        int x=1;
        while(x){
    //        printf("%d %d
    ",g[1],f[1]);
            if(x!=1) K-=(T==0?1:endpos[x]);
            if(K<1) break;
            for(int i=0;i<26;++i) if(ch[x][i]) {
                if(T==0){
                    if(K>g[ch[x][i]]) K-=g[ch[x][i]];
                    else {printf("%c",'a'+i);x=ch[x][i];break;}
                }
                else{
                    if(K>f[ch[x][i]]) K-=f[ch[x][i]];
                    else {printf("%c",'a'+i);x=ch[x][i];break;}
                }
            }
        }
        return 0;
    }
    View Code

    B. 诸神眷顾的幻想乡

    广义后缀自动机。

    看到叶子很少,就有了一条性质:树上的任何一条路径都可以变成从一个叶子走到了另一个叶子。

    于是把原树转化成了若干条串,答案就是这些串的本质不同的子串数。

    跑广义后缀自动机有两种做法:
    建$trie$,接着$bfs$建$sam$,这个点的$lst$是它在$trie$上的$fa$。

    一个串一个串的跑,每次的$lst$置为1。

    和普通$sam$不一样的地方是

    inline void extend(int c){
        int p=lst,np,q,nq;
        if(ch[p][c]){
            q=ch[p][c];
            if(len[q]==len[p]+1) return lst=q,void();
            lst=nq=++cnt; fa[nq]=fa[q]; fa[q]=nq;
            len[nq]=len[p]+1;
            for(int i=0;i<C;++i) ch[nq][i]=ch[q][i];
            for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
        }
        else{
            lst=np=++cnt;len[np]=len[p]+1;
            for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
            if(!p) return fa[np]=1,void();
            q=ch[p][c];
            if(len[q]==len[p]+1) return fa[np]=q,void();
            nq=++cnt;len[nq]=len[p]+1;
            fa[nq]=fa[q];fa[q]=fa[np]=nq;
            for(int i=0;i<C;++i) ch[nq][i]=ch[q][i];
            for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
        }
    }

    当有过这样的$ch[p][c]& & len[q]==len[p]+1$时,直接返回$q$节点。

    还有DC讲过的不要np的情况,但我觉得太难记了,实际上。

    inline void extend(int c){
        int p=lst,np,q,nq;
        if(ch[p][c]&&len[ch[p][c]]==len[ch[p][c]]+1) return lst=ch[p][c],void();
    
        lst=np=++cnt;len[np]=len[p]+1;
        for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
        if(!p) return fa[np]=1,void();
        q=ch[p][c];
        if(len[q]==len[p]+1) return fa[np]=q,void();
        nq=++cnt;len[nq]=len[p]+1;
        fa[nq]=fa[q];fa[q]=fa[np]=nq;
        for(int i=0;i<C;++i) ch[nq][i]=ch[q][i];
        for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
    }

    这样也是可以的,就是不去考虑不建np的情况,这样还比较好理解。

    C. 公共串

    计算几个串的最长公共子串。

    做法:

    用一个串跑$sam$,把其它几个串在它上面跑LCS,因为$sam$包含了所有的子串所以一直跑,一直跑到没有这个节点。

    接着跳$fa$,因为已经匹配了很多了,现在要保留尽可能多的后缀,所以跳最长后缀$fa$,对每个点维护$mx$记录这个串跑到这个点的最长匹配长度

    再维护$mn$表示所有几个串在该点匹配长度的最小值,因为要的是所有串的最长公共子串,有关的一个问题是:

    在维护$mx$时,要进行一步把它的$mx$给$fa$的操作,原因是如果能考虑到这个点,那么就一定能匹配它的$fa$。

    其实我认为还有一步操作是把$fa$的$mx$给它,不过$skyh$说贡献答案的话只需要满足有一个点能有所有串的$mx$就行了,而我们把$mx$给了$fa$就可以在$fa$处统计答案了。

    然而我还有问题....抱歉这道题作假了

    D. 差异

    两个后缀的$lcp$就是这个串翻转之后的$parent tree$上的$lca$的$len$,因为$lca$是它们的最长公共后缀,相当于翻转前的后缀的最长公共前缀。

    这样的话问题转化为统计每个$parent tree$的点作为$lca$的次数了,用它的C-它的儿子们的C就行了。

    在做这道题时还疑问了很久为啥没有实际含义的$nq$也要作为$lca$被统计。

    可以这样理解,因为$nq$是$q$的一个副本,而一切的$q$都追本溯元成为有意义的点了。

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int N=1e6+50;
    int n,point_cnt,lst;
    int len[N],ch[N][26],fa[N],endpos[N],tra[N];
    char s[N];
    vector <int> v[N];
    inline void extend(int c){
        int p=lst,np;np=lst=++point_cnt;
        endpos[np]=1;
        v[len[np]=len[p]+1].push_back(np);
        for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
        if(!p) fa[np]=1;
        else{
            int q=ch[p][c];
            if(len[q]==len[p]+1) fa[np]=q;
            else{
                int nq=++point_cnt;
                fa[nq]=fa[q];fa[np]=fa[q]=nq;
                v[len[nq]=len[p]+1].push_back(nq);
                for(int i=0;i<26;++i) ch[nq][i]=ch[q][i];
                for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
            }
        }
    }
    #define xx endpos[v[i][j]]*(endpos[v[i][j]]-1)/2
    int ans;
    signed main(){
        lst=point_cnt=1;
        scanf("%s",s+1);
        n=strlen(s+1);
        reverse(s+1,s+n+1);
        for(int i=1;i<=n;++i) extend(s[i]-'a');
        ans=(1+n)*n/2*(n-1);
        for(int i=n;i;--i) for(int j=0;j<(int)v[i].size();++j) endpos[fa[v[i][j]]]+=endpos[v[i][j]];
        for(int i=n;i;--i) for(int j=0;j<(int)v[i].size();++j) tra[v[i][j]]+=xx,tra[fa[v[i][j]]]-=xx;
        for(int i=n;i;--i) for(int j=0;j<(int)v[i].size();++j) ans-=2*len[v[i][j]]*tra[v[i][j]];
        printf("%lld
    ",ans);
        return 0;
    }
    View Code

    E. 工艺

    最小表示法$O(n)$。

    或者把原串再接一遍跑$sam$,答案就是从起点开始贪心走$n$步的字符串。

    做题时其实是letong,我又作假题了有疑问,会不会有按照最小字符走到某个节点走不动了的情况呢?

    答案是不会的,因为如果有这样的边即字符,那一定是相当于从第二次接的串出发的,那这样的字符结构一定会有两次出现,因为接了两次,

    那么两次该字符的$endpos$都在这个点上,也就是说没有边了就相当于走前面那个点的边了。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=2e6+50;
    map <int,int> ch[N];
    int n,lst,point_cnt;
    int len[N],fa[N],d[N];
    inline void extend(int c){
        int p=lst,np;np=lst=++point_cnt;
        len[np]=len[p]+1;
        for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
        if(!p) fa[np]=1;
        else{
            int q=ch[p][c];
            if(len[q]==len[p]+1) fa[np]=q;
            else{
                int nq=++point_cnt;
                len[nq]=len[p]+1;
                fa[nq]=fa[q];
                ch[nq]=ch[q];
                fa[np]=fa[q]=nq;
                for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
            }
        }
    }
    int main(){
        lst=point_cnt=1;
        scanf("%d",&n);
        for(int i=1;i<=n;++i) scanf("%d",&d[i]),extend(d[i]);
        for(int i=1;i<=n;++i) extend(d[i]);
        int x=1,cnt=0;
        while(++cnt<=n) printf("%d ",(*ch[x].begin()).first),x=(*ch[x].begin()).second;
        return 0;
    }
    sam
    #include<bits/stdc++.h>
    #define N 600005
    using namespace std;
    int n,s[N];
    inline int rd(){
        register int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)) f=ch=='-'?-1:1,ch=getchar();
        while(isdigit(ch)) x=(x<<1)+(x<<3)+ch-48,ch=getchar();
        return x*f;
    }
    int main(){
        n=rd();
        for(int i=1;i<=n;++i) s[i]=s[n+i]=rd();
        int i=1,j=2,k;
        while(i<=n&&j<=n){
            for(k=0;k<=n&&s[i+k]==s[j+k];k++);
            if(k==n) break;
            if(s[i+k]>s[j+k]){
                i=i+k+1;
                if(i==j) ++i;
            }
            else{
                j=j+k+1;
                if(i==j) ++j;
            }
        }
        int st=min(i,j);
        for(int i=1;i<=n;++i) printf("%d ",s[st+i-1]);
        return 0;
    }
    最小表示法

    F. 生成魔咒

    这么吓唬人 的题这么水。

    求不同本质的串的个数。

    因为我们得知一个点代表的串的长度一定是连续的。

    所以这个点代表的字符串数量就是$len[i]-minlen[i]+1=len[i]-len[fa[i]]$

    把每个点的答案都统计就是答案了。

    数太大了,就用$map$存$ch$就行了。

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int N=2e5+50;
    map <int,int> ch[N];
    int B,point_cnt,lst,ans,n,fa[N],len[N];//,head[N],to[N<<1],nxt[N<<1],deg[N],sta[N],f[N];
    /*inline void lnk(int x,int y){
        to[++B]=y,nxt[B]=head[x],head[x]=B,deg[y]++;
    }*/
    inline void extend(int c){
        int p=lst,np; lst=np=++point_cnt;
        len[np]=len[p]+1;
        for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
        if(!p) fa[np]=1;
        else{
            int q=ch[p][c];
            if(len[q]==len[p]+1) fa[np]=q;
            else{
                int nq=++point_cnt;
                fa[nq]=fa[q];
                ch[nq]=ch[q];
                len[nq]=len[p]+1;
                fa[q]=fa[np]=nq;
                for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
            }
        }
        ans+=len[lst]-len[fa[lst]];
        printf("%lld
    ",ans);
    }
    /*void dfs(int x,int prt){
        f[x]=1;
        for(map <int,int> ::iterator it=ch[x].begin();it!=ch[x].end();++it)
            dfs((*it).second,x),f[x]+=f[(*it).second];
    }*/
    signed main(){
        scanf("%lld",&n); point_cnt=1; lst=1;
        for(int i=1,x;i<=n;++i) scanf("%lld",&x),extend(x);
        //dfs(1,0);
        /*sta[0]=0;
        for(int i=1;i<=point_cnt;++i){if(!deg[i]) sta[++sta[0]]=i;f[i]=1;}
        for(int i=1;i<=sta[0];++i){
            int x=sta[i];
            for(int i=head[x];i;i=nxt[i]){
                f[to[i]]+=f[x];
                if(--deg[to[i]]==0) sta[++sta[0]]=to[i];
            }
        }*/
        //printf("%d
    ",f[1]-1);
        return 0;
    }
    View Code

    G. SubString

    因为要动态维护$endpos$集合,所以要用$lct$动态维护,那么添加一个点时就把$split(1,x)$,然后链加即可。

    中间还有一个问题,就是$nq$,$nq$位置要直接把它的$endpos$赋值为$endpos[q]$。

    那么就是单点修改,链修改,单点查询了。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=2e6+50;
    int Q,n,lst,cnt,mask;
    int cpy[N],endpos[N],len[N],ch[N][26],fa[N],rk[N],buc[N];
    char s[N],str[N];
    inline void update(int x,int delta){
        while(x) endpos[x]+=delta,x=fa[x];
    }
    inline void extend(int c){
        int p=lst,np;np=lst=++cnt;
        endpos[np]=1; len[np]=len[p]+1;
        for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
        if(!p) fa[np]=1;
        else{
            int q=ch[p][c];
            if(len[q]==len[p]+1) fa[np]=q;
            else{
                int nq=++cnt;
                fa[nq]=fa[q],fa[q]=fa[np]=nq;
                endpos[nq]=endpos[q];
                len[nq]=len[p]+1;
                for(int i=0;i<26;++i) ch[nq][i]=ch[q][i];
                for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
            }
        }
        update(fa[np],endpos[np]);
    }
    inline void input(int tmp=mask){
        scanf("%s",str);
        int newlen=strlen(str);
        for(int i=0;str[i];++i){
            tmp=(tmp*131+i)%newlen;
            swap(str[i],str[tmp]);
        }
    }
    int main(){
        lst=cnt=1;
        scanf("%d%s",&Q,s+1);
        n=strlen(s+1);
        for(int i=1;s[i];++i) extend(s[i]-'A');
        for(;Q;--Q){
            scanf("%s",str);
            if(str[0]=='A'){
                input();
                for(int i=0;str[i];++i) extend((s[++n]=str[i])-'A');
            }
            else{
                /*for(int i=1;i<=cnt;++i) endpos[i]=cpy[i];
    
                for(int i=0;i<=n;++i) buc[i]=0;
                for(int i=1;i<=cnt;++i) buc[len[i]]++;
                for(int i=0;i<=n;++i) buc[i]+=buc[i-1];
                for(int i=1;i<=cnt;++i) rk[buc[len[i]]--]=i;
                for(int i=cnt;i;--i) if(fa[rk[i]]) endpos[fa[rk[i]]]+=endpos[rk[i]];*/
    
                input();
                int res=0,root=1;
                for(int i=0;str[i];++i) root=ch[root][str[i]-'A'];
                res=root?endpos[root]:0;
                printf("%d
    ",res);
                mask^=res;
            }
        }
        return 0;
    }
    修改endpos时暴跳fa不用lct->2871ms
    #include<bits/stdc++.h>
    using namespace std;
    const int N=2e6+50;
    int Q,n,lst,cnt,mask;
    int cpy[N],endpos[N],len[N],ch[N][26],fa[N],rk[N],buc[N];
    char s[N],str[N];
    
    struct LCT{
        int prt[N],ch[N][2],sum[N],rev[N],tag[N];
        inline int get(int x){return ch[prt[x]][1]==x;}
        inline int nroot(int x){return ch[prt[x]][0]==x||ch[prt[x]][1]==x;}
        inline void pushrev(int x){
            rev[x]^=1;
            swap(ch[x][0],ch[x][1]);
        }
        inline void pushtag(int x,int Tag){
            tag[x]+=Tag;
            sum[x]+=Tag;
        }
        inline void pushdown(int x){
            if(rev[x]){
                pushrev(ch[x][0]);
                pushrev(ch[x][1]);
                rev[x]=0;
            }
            if(tag[x]){
                pushtag(ch[x][0],tag[x]);
                pushtag(ch[x][1],tag[x]);
                tag[x]=0;
            }
        }
        inline void pushup(int x){}
        inline void rotate(int x){
            int fa=prt[x],gr=prt[fa],k=get(x);
            if(nroot(fa)) ch[gr][get(fa)]=x;prt[x]=gr;
            ch[fa][k]=ch[x][k^1];prt[ch[x][k^1]]=fa;
            ch[x][k^1]=fa;prt[fa]=x;
            pushup(fa);pushup(x);
        }
        void topushdown(int x){
            if(nroot(x)) topushdown(prt[x]);
            pushdown(x);
        }
        inline void splay(int x){
            topushdown(x);
            for(;nroot(x);rotate(x)) if(nroot(prt[x])) rotate(get(x)==get(prt[x])?prt[x]:x);
        }
        inline void access(int x){
            for(int y=0;x;y=x,x=prt[x]) splay(x),ch[x][1]=y,pushup(x);
        }
        inline int findroot(int x){
            access(x); splay(x);
            while(ch[x][0]) pushdown(x),x=ch[x][0];
            return splay(x),x;
        }
        inline void makeroot(int x){
            access(x); splay(x); pushrev(x);
        }
        inline void split(int x,int y){
            makeroot(x); access(y); splay(y);
        }
        inline void link(int x,int y){
            if(!x||!y) return;
            makeroot(x);
            if(findroot(y)==x) return;
            prt[x]=y; pushup(y);
        }
        inline void cut(int x,int y){
            if(!x||!y) return;
            makeroot(x);
            if(findroot(y)!=x||ch[y][0]||prt[y]!=x) return;
            access(y); splay(x);
            ch[x][1]=prt[y]=0; pushup(x);
        }
        inline void exchange(int x,int Tag){
            split(1,x);
            pushtag(x,Tag);
        }
        inline int endpos(int x){
            split(x,x);
            return sum[x];
        }
        inline void special_ex(int x,int Tag){
            split(x,x);
            sum[x]+=Tag;
        }
    }lct;
    
    inline void extend(int c){
        int p=lst,np;np=lst=++cnt;
        len[np]=len[p]+1;
        
        for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
        if(!p) fa[np]=1;
        else{
            int q=ch[p][c];
            if(len[q]==len[p]+1) fa[np]=q;
            else{
                int nq=++cnt;
                lct.cut(q,fa[q]);
                lct.link(nq,fa[q]);
                fa[nq]=fa[q],fa[q]=fa[np]=nq;
                lct.link(q,nq);
                lct.special_ex(nq,lct.endpos(q));
                len[nq]=len[p]+1;
                for(int i=0;i<26;++i) ch[nq][i]=ch[q][i];
                for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
            }
        }
        lct.link(np,fa[np]);
        lct.exchange(np,1);
    }
    
    inline void input(int tmp=mask){
        scanf("%s",str);
        int newlen=strlen(str);
        for(int i=0;str[i];++i){
            tmp=(tmp*131+i)%newlen;
            swap(str[i],str[tmp]);
        }
    }
    int main(){
        lst=cnt=1;
        scanf("%d%s",&Q,s+1);
        n=strlen(s+1);
        for(int i=1;s[i];++i) extend(s[i]-'A');
        for(;Q;--Q){
            scanf("%s",str);
            if(str[0]=='A'){
                input();
                for(int i=0;str[i];++i) extend((s[++n]=str[i])-'A');
            }
            else{
                /*for(int i=1;i<=cnt;++i) endpos[i]=cpy[i];
    
                for(int i=0;i<=n;++i) buc[i]=0;
                for(int i=1;i<=cnt;++i) buc[len[i]]++;
                for(int i=0;i<=n;++i) buc[i]+=buc[i-1];
                for(int i=1;i<=cnt;++i) rk[buc[len[i]]--]=i;
                for(int i=cnt;i;--i) if(fa[rk[i]]) endpos[fa[rk[i]]]+=endpos[rk[i]];*/
    
                input();
                int res=0,root=1;
                for(int i=0;str[i];++i) root=ch[root][str[i]-'A'];
    
                res=root?lct.endpos(root):0;
                printf("%d
    ",res);
                mask^=res;
            }
        }
        return 0;
    }
    lct->9994ms

    其实不是很难打,思路也很简单。毕竟是模板题

    在$hzoj$,暴力碾标算。

    H. Cheat

    题意:m个串,n次询问,每次询问这个串的L0,满足任意一段都$>=L0$。

    把$m$个串建上广义$sam$,发现L0具有二分性,所以二分L0,然后问题就变成了:

    把串分成若干个匹配串和不可匹配串,那么要求的匹配串的长度都必须大于二分的mid,要求$sum$匹配串长度$>=0.9*len$,len为串长。

    不妨设$f[i]$表示考虑到第$i$个字符时的最大匹配长度,那么有

    $f[i]=max(f[i-1],f[j]+i-j),jin [i-match[i],i-mid]$

    $match$是以$i$为结尾的最长前缀,可以通过在$sam$上跑实现。

    对于$f[i]$,$i$点可以不选就是$f[i-1]$,要选就必须选长度$>=mid$,而当长度$>match[i]$时再往前匹配就没有了意义,因为一定匹配不上,所以上下界确定了。

    进一步发现,$i-match[i]$与$i-mid$是单调不降,单调增的,因此可以用单调栈维护,$check$的判断条件就是$f[n]>=0.9*len$,需要向上取整,或者$f[n]*10>=len*9$也可。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=4e6+50;
    int n,m,lst,cnt;
    int len[N],fa[N],ch[N][2],f[N],ret[N];
    char s[N];
    inline void extend(int c){
        if(ch[lst][c]&&len[ch[lst][c]]==len[lst]+1) return lst=ch[lst][c],void();
        int p=lst,np;
        for(len[np=lst=++cnt]=len[p]+1;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
        if(!p) fa[np]=1;
        else{
            int q=ch[p][c];
            if(len[q]==len[p]+1) fa[np]=q;
            else{
                int nq=++cnt;
                len[nq]=len[p]+1;
                fa[nq]=fa[q],fa[q]=fa[np]=nq,ch[nq][0]=ch[q][0],ch[nq][1]=ch[q][1];
                for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
            }
        }
    }
    int lenn;
    struct Node{int pos,val;}que[N];
    inline bool check(int k,int ans=0){
        for(int i=0;i<=lenn;++i) f[i]=0;
        int head=1,tail=0;
        for(int i=1;i<=lenn;++i){
            if(i-k>=0){
                while(head<=tail&&que[tail].val<=f[i-k]-(i-k)) tail--;
                que[++tail]=(Node){i-k,f[i-k]-(i-k)};
            }
            
            while(head<=tail&&que[head].pos<i-ret[i]) head++;
            f[i]=f[i-1];
            if(head<=tail) f[i]=max(f[i],i+que[head].val);
            ans=max(ans,f[i]);
        }
        int d=0.9*lenn+0.99;
        return ans>=d;
    }
    inline void solve(){
        scanf("%s",s+1);
        lenn=strlen(s+1);
        int LCS=0,root=1;
        for(int i=1;s[i];++i)
            if(ch[root][s[i]-'0'])
                root=ch[root][s[i]-'0'],
                ret[i]=++LCS;
                
            else{
                for(;root&&!ch[root][s[i]-'0'];root=fa[root]) ;
                if(!root) root=1,ret[i]=LCS=0;
                else
                    LCS=min(LCS,len[root]),
                    root=ch[root][s[i]-'0'],
                    ret[i]=++LCS;
                
            }
        int l=0,r=lenn;
        while(l<r){
            int mid=(l+r+1)>>1;
            if(check(mid)) l=mid;
            else r=mid-1;
        }
        printf("%d
    ",l);
    }
    int main(){
        lst=cnt=1;
        scanf("%d%d",&n,&m);
        while(m--){
            scanf("%s",s);
            lst=1;
            for(int i=0;s[i];++i) extend(s[i]-'0');
        }
        while(n--) solve();
        return 0;
    }
    View Code

    I. 品酒大会

    题意:求所有后缀对的$lcp$。

    想点对不好想,不妨考虑每个$parent tree$上的点作为$lcp$时的贡献,那么题目实际上就和D.差异类似了。

    #include<bits/stdc++.h>
    #define int long long 
    using namespace std;
    const int N=6e5+50,inf=0x3f3f3f3f3f3f3f3f;
    int n,lst,cnt;
    int a[N],len[N],mx[N],mn[N],ch[N][26],fa[N],endpos[N];
    char s[N];
    vector <int> son[N];
    pair <int,int> ans[N];
    inline void extend(int c){
        int p=lst,np; np=lst=++cnt;
        len[np]=len[p]+1;
        endpos[np]=1;
        mx[np]=mn[np]=a[n];
        for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
        if(!p) fa[np]=1;
        else{
            int q=ch[p][c];
            if(len[q]==len[p]+1) fa[np]=q;
            else{
                int nq=++cnt;
                len[nq]=len[p]+1;
                fa[nq]=fa[q],fa[np]=fa[q]=nq;
                for(int i=0;i<26;++i) ch[nq][i]=ch[q][i];
                for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
            }
        }
    }
    inline void dfs(int x){
        for(int i=0;i<(int)son[x].size();++i){
            int u=son[x][i];
            dfs(u);
            ans[len[x]].first+=endpos[x]*endpos[u];
            endpos[x]+=endpos[u];
            if(mx[x]!=-inf&&mx[u]!=-inf) ans[len[x]].second=max(ans[len[x]].second,mx[x]*mx[u]);
            if(mn[x]!=inf&&mn[u]!=inf) ans[len[x]].second=max(ans[len[x]].second,mn[x]*mn[u]);
            mx[x]=max(mx[x],mx[u]);
            mn[x]=min(mn[x],mn[u]);
        }
    //    printf("----------------%d %d %d
    ",x,len[x],endpos[x]);
    }
    signed main(){
        lst=cnt=1;
        scanf("%lld%s",&n,s+1); int T=strlen(s+1);
        reverse(s+1,s+T+1);
        for(int i=0;i<=n*2;++i) mx[i]=-inf,mn[i]=inf;
        for(int i=0;i<=n*2;++i) ans[i].second=-inf;
        for(int i=n;i;--i) scanf("%lld",&a[i]);
        for(n=1;s[n];++n) extend(s[n]-'a');--n;
        for(int i=1;i<=cnt;++i) son[fa[i]].push_back(i);
        dfs(1);
        for(int i=n-1;~i;--i) ans[i].first+=ans[i+1].first,ans[i].second=max(ans[i+1].second,ans[i].second);
        for(int i=0;i<n;++i) printf("%lld %lld
    ",ans[i].first,ans[i].second==-inf?0:ans[i].second);
        return 0;
    }
    View Code

    J. 你的名字

  • 相关阅读:
    [转]linux下的fms2流媒体服务器搭建六部曲之四格式转换篇
    Linux 信号signal处理函数
    FMS在linux下安装时的问题处理
    Linux 下让你的C程序在后台运行
    Linux 信号signal处理机制
    Flash Media Server 2 无限制许可文件
    JavaScript动态控制网页元素事件
    自定义MessageBox的窗口颜色,字体等属性
    [转]linux下的fms2流媒体服务器搭建六部曲之一ffmpeg安装篇
    [转]linux下的fms2流媒体服务器搭建六部曲之三FlashMediaServer安装篇
  • 原文地址:https://www.cnblogs.com/hzoi2018-xuefeng/p/12112928.html
Copyright © 2011-2022 走看看