SAM 后缀自动机

bzoj3676 回文串

题目大意:给定一个字符串,求其中某种回文串的长度*出现次数的最大值。

思路:建立后缀自动机,用manachur求出本质不同的回文串(也就是比较使pp[i]+1的时候),然后在后缀自动机上的相应节点往上找fa,统计siz。

(这道题目中manacher不能加字符(会mle),所以要奇偶分别匹配,但是偶数的时候是有问题的。)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 600005
#define maxsiz 26
#define up 20
#define LL long long
using namespace std;
char ss[N];
int sz,last,ll,pp[N]={0};
LL ans=0LL;
struct use{
    int ch[N][maxsiz],fa[N],mx[N],siz[N],que[N],id[N],anc[N][up];
    int idx(char ch){return ch-'a';}
    void extend(char x,int ii){
        int i,j,u,p,q,np,nq;
        u=idx(x);p=last;id[ii]=np=++sz;
        mx[np]=mx[p]+1;siz[np]=1;
        while(p && !ch[p][u]){ch[p][u]=np;p=fa[p];}
        if (!p) fa[np]=1;
        else{
            q=ch[p][u];
            if (mx[p]+1==mx[q]) fa[np]=q;
            else{
                nq=++sz;mx[nq]=mx[p]+1;
                memcpy(ch[nq],ch[q],sizeof(ch[q]));
                fa[nq]=fa[q];fa[q]=fa[np]=nq;
                while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];}
            }
        }last=np;}
    void pre(){
        int i,j;
        memset(pp,0,sizeof(pp));
        for (i=1;i<=sz;++i) ++pp[mx[i]];
        for (i=1;i<=ll;++i) pp[i]+=pp[i-1];
        for (i=sz;i;--i) que[pp[mx[i]]--]=i;
        for (i=sz;i;--i) siz[fa[que[i]]]+=siz[que[i]];
        memset(anc[0],0,sizeof(anc[0]));
        for (i=1;i<=sz;++i){
            anc[que[i]][0]=fa[que[i]];
            for (j=1;j<up;++j) anc[que[i]][j]=anc[anc[que[i]][j-1]][j-1];
        }}
    void query(int l,int r){
        int u,v,i,j;u=id[r];
        for (i=up-1;i>=0;--i)
          if (mx[anc[u][i]]>=r-l+1) u=anc[u][i];
        ans=max(ans,(LL)siz[u]*(LL)(r-l+1));
    }
}sam;
void work(){
    int i,j,mx,id;
    memset(pp,0,sizeof(pp));
    for (mx=0,i=1;i<ll;++i){
        if (mx>i) pp[i]=min(pp[2*id-i],mx-i);
        else{pp[i]=1;sam.query(i,i);}
        for (;ss[i-pp[i]]==ss[i+pp[i]];++pp[i])
            sam.query(i-pp[i],i+pp[i]);
        if (pp[i]+i>mx){mx=pp[i]+i;id=i;}
    }memset(pp,0,sizeof(pp));
    for (mx=0,i=1;i<ll;++i){
        if (mx>i) pp[i]=min(pp[2*id-i-1],mx-i);
        else pp[i]=0;
        for (;ss[i-pp[i]]==ss[i+pp[i]+1];++pp[i])
            sam.query(i-pp[i],i+pp[i]+1);
        if (pp[i]+i>mx){mx=pp[i]+i;id=i;}
    }
}
int main(){
    int i,j,n;
    scanf("%s",ss+1);ss[0]='&';
    ll=strlen(ss+1);ss[++ll]='#';
    for (sz=last=1,i=1;i<ll;++i) sam.extend(ss[i],i);
    sam.pre();work();printf("%I64d\n",ans);
}
View Code

 

bzoj3926 诸神眷顾的幻想乡

题目大意:给定一棵树和每个节点的颜色,求颜色序列不同的链的个数(AB和BA是不同的)。

思路:因为题目保证了每个节点最多有20个相邻节点,所以可以从每个叶子dfs,然后加点,这里有一个小优化:如果这个节点和父亲的关系没变就可以不用新开节点。对于每个叶子节点出去的串都是从根开始建的,最后答案就是sigma mx[i]-mx[fa[i]]。

注意:因为一个点会加入多次数组要开的足够大

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200005
#define M 2000005
#define ms 10
#define LL long long
using namespace std;
int point[N]={0},next[N],en[N],co[N],tot=0,c,du[N],sz,cnt[M]={0},id[N];
struct use{
    int ch[M][ms],fa[M],mx[M],siz[M],que[M];
    int extend(int pp,int u){
        int i,j,p,np,q,nq;np=ch[p=pp][u];
        if (np && mx[np]==mx[pp]+1) return np;
        np=++sz;mx[np]=mx[p]+1;siz[np]=1;
        while(p && !ch[p][u]){ch[p][u]=np;p=fa[p];}
        if (!p) fa[np]=1;
        else{
            q=ch[p][u];
            if (mx[q]==mx[p]+1) fa[np]=q;
            else{
                nq=++sz;mx[nq]=mx[p]+1;
                memcpy(ch[nq],ch[q],sizeof(ch[q]));
                fa[nq]=fa[q];fa[q]=fa[np]=nq;
                while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];}
            }
        }return np;}
    LL calc(){
        int i,j;LL ans=0;
        for (i=2;i<=sz;++i) ans+=(LL)(mx[i]-mx[fa[i]]);
        return ans;} 
}sam;
void add(int u,int v){
    next[++tot]=point[u];point[u]=tot;en[tot]=v;
    next[++tot]=point[v];point[v]=tot;en[tot]=u;
    ++du[u];++du[v];}
void dfs(int u,int ff,int len){
    int i,j,v;id[u]=sam.extend(id[ff],co[u]);
    for (i=point[u];i;i=next[i]){
        if ((v=en[i])==ff) continue;
        dfs(v,u,len+1);
    }
}
int main(){
    int i,j,u,v,n,m;scanf("%d%d",&n,&c);
    for (i=1;i<=n;++i) scanf("%d",&co[i]);
    for (i=1;i<n;++i){scanf("%d%d",&u,&v);add(u,v);}
    sz=id[0]=1;
    for (i=1;i<=n;++i)
      if (du[i]==1) dfs(i,0,1);
    printf("%I64d\n",sam.calc());
}
View Code

 

bzoj3998 弦论

题目大意:求给定字符串第k小的字串(T=0的时候,不同位置的相同子串算一个;T=1的时候,不同位置的相同子串算多个)。

思路:建后缀自动机,求right集合大小(siz),更新出每个点后面有多少个子串(两种),T=0的是1、T=1的是siz[i],1号点的都是0。然后每位每位的求就可以了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1000005
#define M 5000000
#define mz 26
#define LL long long
using namespace std;
int last,sz,pp[N]={0},l,point[N],next[M],en[M],du[N]={0},tot=0;
char ss[N];
void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;++du[v];}
struct use{
    int ch[N][mz],fa[N],mx[N],que[N];
    LL siz[N],fi[2][N];
    int idx(char x){return x-'a';}
    void extend(char x){
        int u,p,q,np,nq;
        u=idx(x);p=last;np=++sz;
        mx[np]=mx[p]+1;siz[np]=1LL;
        while(p && !ch[p][u]){ch[p][u]=np;p=fa[p];}
        if (!p) fa[np]=1;
        else{
            q=ch[p][u];
            if (mx[p]+1==mx[q]) fa[np]=q;
            else{
                nq=++sz;mx[nq]=mx[p]+1;
                memcpy(ch[nq],ch[q],sizeof(ch[q]));
                fa[nq]=fa[q];fa[q]=fa[np]=nq;
                while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];}
            }
        }last=np;}
    void pre(){
        int i,j;
        for (i=1;i<=sz;++i){
            ++pp[mx[i]];
            for (j=0;j<mz;++j)
                if (ch[i][j]) add(ch[i][j],i);
        }for (i=1;i<=l;++i) pp[i]+=pp[i-1];
        for (i=sz;i;--i) que[pp[mx[i]]--]=i;
        for (i=sz;i;--i) siz[fa[que[i]]]+=siz[que[i]];
    }
    void topu(){
        int i,u,v,head=0,tail=0;
        for (i=1;i<=sz;++i){
          if (!du[i]) que[++tail]=i;
          fi[0][i]=(i==1 ? 0LL : 1LL);
          fi[1][i]=(i==1 ? 0LL : siz[i]);
        }while(head<tail){
            u=que[++head];
            for (i=point[u];i;i=next[i]){
                if (!(--du[v=en[i]])) que[++tail]=v;
                fi[0][v]+=fi[0][u];fi[1][v]+=fi[1][u];
            }
        }
    }
    int work(int t,LL k){
        int i,u,v;l=0;
        if (k>fi[t][u=1]) return 0;
        while(u){
            if (k<=0) return 1;
            for (i=0;i<mz;++i){
                if (!(v=ch[u][i])) continue;
                if (k>fi[t][v]) k-=fi[t][v];
                else{
                    ss[l++]=i+'a';u=v;
                    k-=(!t ? 1 : siz[u]);
                    break;}
            }if (i>=mz) break;
        }return 1;}
}sam;
int main(){
    int i,t;LL k;scanf("%s",ss);l=strlen(ss);
    for (i=0,sz=last=1;i<l;++i) sam.extend(ss[i]);
    sam.pre();sam.topu();
    scanf("%d%I64d",&t,&k);
    if (!sam.work(t,k)) printf("-1\n");
    else{
        for (i=0;i<l;++i) putchar(ss[i]);
        printf("\n");}
}
View Code

 

bzoj4032 最短不公共子串

题目大意:求a的最短的子串/子序列,不是b的子串/子序列。

思路:1)a的子串、b的子串,fi[i][j]表示a到i、b到j的最长公共子串,答案是i和所有j的最大值的最小值+1;2)a的子串、b的子序列,fi[i][j]表示a到i、b到j的最长公共子串,更新的时候略有不同,可以由x=0~j-1的fi[i-1][x]更新fi[i][j],最后答案还是最大值的最小值+1;3)a的子序列、b的子串,对b建后缀自动机,在后缀自动机上dp,ff[i]表示匹配到i这个节点和a的最大匹配长度,答案是ff[i]的最小值+1;4)a的子序列、b的子序列,ff[i]表示b到i和a的最大匹配长度,类似背包优化,第二维倒着循环,记录b中每位之后每个字母的最前出现位置,然后更新,答案是ff[i]的最小值+1。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 4005
#define mz 26
using namespace std;
int la,lb,fi[N][N],last,sz,gi[N][mz],cc[mz],ff[N];
char a[N],b[N];
int calc1(){
    int i,j,ci,mn;mn=N;
    memset(fi,0,sizeof(fi));
    for (i=1;i<=la;++i){
        for (ci=0,j=1;j<=lb;++j){
            if (a[i]==b[j]) fi[i][j]=fi[i-1][j-1]+1;
            ci=max(ci,fi[i][j]);
        }if (ci!=i) mn=min(mn,ci);
    }return (mn==N ? -1 : mn+1);
}
int calc2(){
    int i,j,bi,ci,mn;mn=N;
    memset(fi,0,sizeof(fi));
    for (i=1;i<=la;++i){
        for (bi=ci=0,j=1;j<=lb;++j){
            if (a[i]==b[j]) fi[i][j]=bi+1;
            ci=max(ci,fi[i][j]);
            bi=max(bi,fi[i-1][j]);
        }if (ci!=i) mn=min(mn,ci);
    }return (mn==N ? -1 : mn+1);
}
struct use{
    int fa[N],ch[N][mz],mx[N],po[N];
    int idx(char ch){return ch-'a';}
    void extend(char c,int x){
        int u,p,q,np,nq;
        u=idx(c);p=last;np=++sz;
        mx[np]=mx[p]+1;
        while(p && !ch[p][u]){ch[p][u]=np;p=fa[p];}
        if (!p) fa[np]=1;
        else{
            q=ch[p][u];
            if (mx[q]==mx[p]+1) fa[np]=q;
            else{
                nq=++sz;mx[nq]=mx[p]+1;
                memcpy(ch[nq],ch[q],sizeof(ch[q]));
                fa[nq]=fa[q];fa[q]=fa[np]=nq;
                while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];}
            }
        }last=np;po[x]=np;
    }
    int calc3(){
        int i,j,k,mn,ci;mn=N;last=sz=1;
        for (i=1;i<=lb;++i) extend(b[i],i);
        memset(ff,60,sizeof(ff));ff[1]=0;
        for (i=1;i<=la;++i)
          for (j=1;j<=sz;++j){
              if (!(ci=ch[j][idx(a[i])])) mn=min(mn,ff[j]);
              else ff[ci]=min(ff[ci],ff[j]+1);
          }
        return (mn==N ? -1 : mn+1);
    }
}sam;
int calc4(){
    int i,j,mn,ci;mn=N;
    for (i=0;i<mz;++i) cc[i]=lb+1;
    for (i=lb;i>=0;--i){
        for (j=0;j<mz;++j) gi[i][j]=cc[j];
        cc[b[i]-'a']=i;
    }memset(ff,60,sizeof(ff));ff[0]=0;
    for (i=1;i<=la;++i)
        for (j=lb;j>=0;--j){
            if ((ci=gi[j][a[i]-'a'])>lb) mn=min(mn,ff[j]);
            else ff[ci]=min(ff[ci],ff[j]+1);
        }
    return (mn==N ? -1 : mn+1);
}
int main(){
    scanf("%s%s",a+1,b+1);la=strlen(a+1);lb=strlen(b+1);
    printf("%d\n%d\n%d\n%d\n",calc1(),calc2(),sam.calc3(),calc4());
}
View Code

 

bzoj4180 字符串计数

题目大意:一个字符串S只由ABCD组成,每次从S中找一个字串加入T,求一个长度为n的最小操作次数的最大值。

思路:考虑二分操作次数,然后判断当前操作次数下最小的字符串长度。fi[i][j]表示i开头j结尾的最短长度(因为考虑转移,所以表示成i开头,j结尾就不是字串的最短长度比较好做),这个数组可以用后缀自动机拓扑一边求出来,但因为是i开头j结尾不是字串的长度,所以要从rt开始的串才行。对于判断,矩乘,然后看能否有一个i开头j结尾的长度小于n。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 500000
#define LL long long
#define inf 0x7fffffffffffffffLL
using namespace std;
char ss[N];
LL n;
int last,sz;
char in(){
    char ch=getchar();
    while(ch<'A'||ch>'Z') ch=getchar();
    return ch;}
struct mat{
    LL num[4][4];
    void init(){
        memset(num,0,sizeof(num));
        for (int i=0;i<4;++i) num[i][i]=1LL;}
    mat operator*(const mat &x)const{
        int i,j,k;mat c;
        for (i=0;i<4;++i)
          for (j=0;j<4;++j){
              c.num[i][j]=inf;
              for (k=0;k<4;++k) c.num[i][j]=min(c.num[i][j],num[i][k]+x.num[k][j]);
          }return c;}
}ji;
mat mi(mat x,LL y){
    mat a;a.init();
    for (;y;y/=2LL){
        if (y%2LL) a=a*x;
        x=x*x;
    }return a;}
struct use{
    int fa[N],ch[N][4],mx[N];LL mn[N][4];
    bool vi[N];
    void init(){
        memset(fa,0,sizeof(fa));
        memset(ch,0,sizeof(ch));
        memset(mx,0,sizeof(mx));
        memset(vi,false,sizeof(vi));}
    int idx(char u){return u-'A';}
    void extend(char c){
        int u,p,q,np,nq;u=idx(c);
        p=last;np=++sz;mx[np]=mx[p]+1;
        while(p && !ch[p][u]){ch[p][u]=np;p=fa[p];}
        if (!p) fa[np]=1;
        else{
            q=ch[p][u];
            if (mx[q]==mx[p]+1) fa[np]=q;
            else{
                nq=++sz;mx[nq]=mx[p]+1;
                memcpy(ch[nq],ch[q],sizeof(ch[q]));
                fa[nq]=fa[q];fa[q]=fa[np]=nq;
                while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];}
            }
        }last=np;}
    void dfs(int u){
        if (vi[u]) return;vi[u]=true;
        int i,j;
        for (i=0;i<4;++i){
            if (ch[u][i]) dfs(ch[u][i]);
            mn[u][i]=inf/3LL;
        }for (i=0;i<4;++i){
          if (!ch[u][i]){mn[u][i]=1LL;continue;}
          for (j=0;j<4;++j) mn[u][j]=min(mn[u][j],mn[ch[u][i]][j]+1LL);
        }
    }
}sam;
bool judge(LL x){
    int i,j;
    mat ci=mi(ji,x);
    for (i=0;i<4;++i)
      for (j=0;j<4;++j)
          if (ci.num[i][j]+1LL<=n) return true;
    return false;}
int main(){
    int i,j,ll;LL l,r,mid,ans;scanf("%I64d",&n);
    scanf("%s",ss);ll=strlen(ss);sam.init();
    for (last=sz=1,i=0;i<ll;++i) sam.extend(ss[i]);
    sam.dfs(1);l=0;r=n+1;
    for (i=0;i<4;++i)
      for (j=0;j<4;++j) ji.num[i][j]=sam.mn[sam.ch[1][i]][j];
    while(l<=r){
        mid=(l+r)/2LL;
        if (judge(mid)){l=mid+1;ans=mid;}
        else r=mid-1;
    }printf("%I64d\n",ans+1);
}
View Code

 

bzoj4545 DQS的trie

题目大意:维护一个trie,询问trie上本质不同的子串个数和某种串出现的个数。

思路:建后缀自动机,用lct维护子树的大小的方法是在链上放标记(维护一些点的权值表示子树大小)。全局不同子串的个数用全局变量=mx[x]-mx[fa[x]]就可以了;某个串出现次数是从后缀自动机上顺着走,最后一个点的子树大小就是答案。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200005
#define up 3
#define LL long long
using namespace std;
int in(){
    char ch=getchar();int x=0;
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';ch=getchar();
    }return x;}
int chin(){
    char ch=getchar();
    while(ch<'a'||ch>'z') ch=getchar();
    return ch-'a';}
int point[N],next[N],en[N],va[N],tot=0,ff[N]={0},id[N]={0},sz,co[N];
char ss[N];
LL ans=0LL;
void add(int u,int v,int c){
    next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=c;
    next[++tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=c;}
struct lct{
    int fa[N],ch[N][2],sz[N],del[N],que[N];
    bool isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
    bool isd(int x){return ch[fa[x]][1]==x;}
    void paint(int x,int y){if (x){del[x]+=y;sz[x]+=y;}}
    void pushdown(int x){
        int l,r;l=ch[x][0];r=ch[x][1];
        if (del[x]){paint(l,del[x]);paint(r,del[x]);del[x]=0;}
    }
    void rotate(int x){
        int y,z,l,r;z=fa[y=fa[x]];
        if (ch[y][0]==x) l=0;
        else l=1; r=l^1;
        if (!isroot(y)){
            if (y==ch[z][0]) ch[z][0]=x;
            else ch[z][1]=x;
        }fa[x]=z;fa[y]=x;fa[ch[x][r]]=y;
        ch[y][l]=ch[x][r];ch[x][r]=y;
    }
    void splay(int x){
        int i,y,z,qz;que[qz=1]=x;
        for (i=x;!isroot(i);i=fa[i]) que[++qz]=fa[i];
        for (;qz;--qz){
            pushdown(que[qz]);
        }while(!isroot(x)){
            z=fa[y=fa[x]];
            if (!isroot(y)){
                if (isd(y)==isd(x)) rotate(y);
                else rotate(x);
            }rotate(x);
        }
    }
    void access(int x){int y=0;while(x){splay(x);ch[x][1]=y;y=x;x=fa[x];}}
    void link(int x,int y){fa[x]=y;access(y);splay(y);paint(y,sz[x]);}
    void cut(int x){
        access(x);splay(x);paint(ch[x][0],-sz[x]);
        fa[ch[x][0]]=0;ch[x][0]=0;
    }
}tr;
struct use{
    int fa[N],ch[N][up],mx[N];
    int extend(int pp,int u){
        int i,j,p,np,q,nq;p=pp;
        mx[np=++sz]=mx[p]+1;tr.sz[np]=1;
        while(p&&!ch[p][u]){ch[p][u]=np;p=fa[p];}
        if (!p){fa[np]=1;tr.link(np,1);ans+=(LL)(mx[np]-mx[1]);}
        else{
            q=ch[p][u];
            if (mx[q]==mx[p]+1){fa[np]=q;ans+=(LL)(mx[np]-mx[q]);tr.link(np,q);}
            else{
                nq=++sz;mx[nq]=mx[p]+1;
                memcpy(ch[nq],ch[q],sizeof(ch[q]));
                fa[nq]=fa[q];tr.link(nq,fa[q]);
                ans+=(LL)(mx[nq]-mx[fa[nq]]);
                ans-=(LL)(mx[q]-mx[fa[q]]);
                tr.cut(q);fa[q]=fa[np]=nq;
                ans+=(LL)(mx[q]-mx[nq]+mx[np]-mx[nq]);
                tr.link(q,nq);tr.link(np,nq);
                while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];}
            }
        }return np;}
    int query(){
        int i,l,u=1;l=strlen(ss);
        for (i=0;i<l;++i){
            if (!ch[u][ss[i]-'a']) return 0;
            u=ch[u][ss[i]-'a'];
        }tr.splay(u);return tr.sz[u];
    }
}sam;
void dfs(int u,int f,int k){
    int i,v;ff[u]=f;
    if (k){
        id[u]=sam.extend(id[f],co[u]);
    }for (i=point[u];i;i=next[i]){
        if ((v=en[i])==f||ff[v]) continue;
        co[v]=va[i];dfs(v,u,k+1);
    }
}
int main(){
    int n,i,j,m,u,v,c,op,rt,si;
    in();n=in();
    for (i=1;i<n;++i){
        u=in();v=in();c=chin();
        add(u,v,c);
    }sz=id[1]=1;dfs(1,0,0);m=in();
    while(m--){
        op=in();
        if (op==1) printf("%I64d\n",ans);
        if (op==2){
            rt=in();si=in();
            for (i=1;i<si;++i){
                u=in();v=in();c=chin();
                add(u,v,c);
            }dfs(rt,ff[rt],0);
        }if (op==3){
            scanf("%s",ss);
            printf("%d\n",sam.query());
        }
    }
}
View Code

 

posted @ 2016-01-23 21:01  Rivendell  阅读(877)  评论(0编辑  收藏  举报