• 「后缀自动机」


    前言

    这比后缀数组难啊。

    但似乎其实我并不觉得比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. 你的名字

  • 相关阅读:
    Encryption (hard) CodeForces
    cf 1163D Mysterious Code (字符串, dp)
    AC日记——大整数的因子 openjudge 1.6 13
    AC日记——计算2的N次方 openjudge 1.6 12
    Ac日记——大整数减法 openjudge 1.6 11
    AC日记——大整数加法 openjudge 1.6 10
    AC日记——组合数问题 落谷 P2822 noip2016day2T1
    AC日记——向量点积计算 openjudge 1.6 09
    AC日记——石头剪刀布 openjudge 1.6 08
    AC日记——有趣的跳跃 openjudge 1.6 07
  • 原文地址:https://www.cnblogs.com/hzoi2018-xuefeng/p/12112928.html
Copyright © 2020-2023  润新知