「后缀自动机」

前言

这比后缀数组难啊。

但似乎其实我并不觉得比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\n",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\n",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\n",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\n",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;
}

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\n",ans);
    return 0;
}

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\n",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\n",f[1]-1);
    return 0;
}

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\n",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\n",res);
            mask^=res;
        }
    }
    return 0;
}

lct->9994ms

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

在$hzoj$,暴力碾标算。

H. Cheat

I. 品酒大会

J. 你的名字

原文地址:https://www.cnblogs.com/hzoi2018-xuefeng/p/12112928.html

时间: 2024-10-10 12:28:41

「后缀自动机」的相关文章

后缀自动机练习专题

后缀自动机练习专题 一些比较有用的东东: (1) \(\text{sam}\) 上一条从初始状态出发的路径对应一个子串 (2) \(\text{parent}\) 树上一个节点能表示的最长的串对应一个前缀/后缀 (3) \(len(u)\) 表示节点 \(u\) 能表示的最长串的长度 (4) \(fa(u)\) 表示节点 \(u\) 的后缀链接指向的节点,也就是其在 \(\text{parent}\) 树上的父亲 (5) 表示两个后缀的公共前缀的节点是两个后缀在 \(\text{parent}\

「C语言」常量和变量的表示及应用

先发布,还在修改完善中.. 在程序运行中,其值不能改变的量成为常量.在基本数据类型中,常量可分为整型常量.实型常量.符号常量和字符型常量(包括字符常量和字符串常量),现分别介绍如下: 整型常量 即整常数,由一个或多个数字组成,可以带正负号 C语言中整型常量可用十进制.八进制和十六进制3种形式表示 十进制整数:由0~9数字组成,不能以0开始,没有前缀 八进制整数:以0为前缀,其后由0~7的数字组成,没有小数部分 十六进制整数:以0x或0X开头,其后由0~9的数字和a~f(或A~F字母组成) 另外长

微信小程序开发基础(一)「配置」与「逻辑层」

微信小程序作为微信生态重要的一环,在实际生活.工作.商业中的应用越来越广泛.想学习微信小程序开发的朋友也越来越多,本文将在小程序框架的基础上就微信小程序项目开发所必需的基础知识及语法特点进行了详细总结和阐述,包括配置.函数.语法.事件及其处理.数据绑定.模块.样式等.想开发小程序,这些内容是必须掌握的. 全文知识结构预览: 一.程序配置: 1.全局配置:2.页面配置 二.逻辑层: 1.程序注册:App()方法:2.页面注册:Page()方法:3.模块与调用:4.微信原生API 三.视图层(将在单

P4930「FJ2014集训」采药人的路径

题目:P4930「FJ2014集训」采药人的路径 思路: 这篇不算题解,是让自己复习的,什么都没说清楚. 很久没有写点分治了,以前为了赶课件学的太急,板子都没打对就照着题解写题,导致学得很不扎实. 这道题差不多是在郭老师的指导下一点点凑出来的,还是没能自己完整写出一道题,惭愧. 这道题大意是:给出一棵边权为0/1的树,求满足以下条件的路径总数:0的个数等于1的个数,且路径上存在一点到路径两端也满足该条件. 这种求路径总数的题,可以想到用点分治. 把0看作-1,就可以转化为路径边权和为0. 如果没

「AHOI2014/JSOI2014」拼图

「AHOI2014/JSOI2014」拼图 传送门 看到 \(n \times m \le 10^5\) ,考虑根号分治. 对于 \(n < m\) 的情况,我们可以枚举最终矩形的上下边界 \(tp, bt\),那么我们发现最终矩形一定是由所有满足从第 \(tp\) 行到第 \(bt\) 行都是白格子的矩形顺次连接,并且两端再各自接上一个最大的前缀和一个最大的后缀构成的. 这个我们可以 \(O(m)\) 地算. 总复杂度就是 \(O(n^2m)\),也就是一个根号级别的. 对于 \(n \ge

怎样将「插件化」接入到项目之中?

本期移动开发精英社群讨论的主题是「插件化」,上网查了一下,发现一篇 CSDN 博主写的文章<Android 使用动态载入框架DL进行插件化开发>.此处引用原作者的话: 随着应用的不断迭代,应用的体积不断增大,项目越来越臃肿,冗余添加.项目新功能的加入,无法确定与用户匹配性,发生严重异常往往牵一发而动全身,仅仅能紧急公布补丁版本号,强制用户进行更新.结果频繁的更新.反而easy减少用户使用黏性,或者是公司业务的不断发展,同系的应用越来越多,传统方式须要通过用户量最大的主项目进行引导下载并安装.

hiho一下第128周 后缀自动机二&#183;重复旋律5

#1445 : 后缀自动机二·重复旋律5 时间限制:10000ms 单点时限:2000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数列. 现在小Hi想知道一部作品中出现了多少不同的旋律? 解题方法提示 输入 共一行,包含一个由小写字母构成的字符串.字符串长度不超过 1000000. 输出 一行一个整数,表示答案. 样例输入 aab 样例输出 5 解题方法提示 小Hi:本周的题目其实就是给定一个字符串S,要求出S的所有不同子串的数

后缀自动机总结

后缀自动机是一种确定性有限自动机(DFA),它可以且仅可以匹配一个给定串的任意后缀. 构造一个可以接受一个给定串的所有后缀的不确定性有限自动机(NFA)是很容易的,我们发现我们用通用的将NFA转换成对应DFA的算法转换出来的DFA的状态数都很小(O(n)级别的,远远达不到指数级别).于是,人们就开始研究这种特殊的NFA,并提出了在线增量算法,用O(n)的时间复杂度构造该NFA的DFA.在转换过程中,DFA中对应的NFA中的状态集合其实就是我们的right集合.——————以上在胡扯———————

BZOJ 2946 Poi2000 公共串 后缀自动机

题目大意:求n个串的最长公共子串 太久没写SAM了真是-- 将第一个串建成后缀自动机,用其它的串进去匹配 每个节点记录每个串在上面匹配的最大长度 那么这个节点对答案的贡献就是所有最大长度的最小值 对所有贡献取最大就行了= = 这最大最小看着真是别扭 #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define M 10100 using namesp