「后缀自动机」

前言

这比后缀数组难啊。

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

题意: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),j\in [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\n",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\n",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\n",ans[i].first,ans[i].second==-inf?0:ans[i].second);
    return 0;
}
View Code

 

J. 你的名字

posted @ 2019-12-28 20:22  _xuefeng  阅读(263)  评论(0编辑  收藏  举报