字符串做题笔记

 

AC自动机:

P5357 【模板】AC自动机(二次加强版)

·不要像以前一样习惯性把trie树的根设为1,从0开始的话后面getfail比较方便。

·trie树的节点编号是无序的,统计答案需要dfs或者拓扑,按编号循环显然是错的。

#include
#include
#include
#include
using namespace std;
const int S=2e6+10,N=2e5+10;
int n,cnt[N],now,tot,len,tree[N][27],ed[N],num[N],fail[N];
char s[S];
int head[N],Next[N],tot1,ver[N],du[N];
void add(int x,int y){
    ver[++tot1]=y;
    Next[tot1]=head[x];
    head[x]=tot1;
} 
void insert(int x){
    now=0;
    len=strlen(s+1);
    for(int i=1;i<=len;i++){
        if(!tree[now][s[i]-'a'])tree[now][s[i]-'a']=++tot;
        now=tree[now][s[i]-'a'];
    }
    ed[now]=1;
    cnt[x]=now;
}
queue<int>q;
void getfail(){
    for(int i=0;i<26;i++){
        if(tree[0][i]){
            fail[tree[0][i]]=0;
            q.push(tree[0][i]);
            add(tree[0][i],0);
            du[0]++;
        }
    }
    while(q.size()){
        int u=q.front();
        q.pop();
        for(int i=0;i<26;i++){
            int v=tree[u][i];
            if(v){
                fail[v]=tree[fail[u]][i];
                q.push(v);
                add(v,fail[v]);
                du[fail[v]]++;
            }
            else{
                tree[u][i]=tree[fail[u]][i];
            }
        }
    }
}
void AC(){
    now=0;
    len=strlen(s+1);
    for(int i=1;i<=len;i++){
        int v=tree[now][s[i]-'a'];
        num[v]++;
        now=v;
    }
}
int main()
{
//    freopen("1.in","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%s",s+1);
        insert(i);
    }
    getfail();
    scanf("%s",s+1);
    AC();
    for(int i=1;i<=tot;i++){
        if(!du[i])q.push(i);
    }
    while(q.size()){
        int u=q.front();
        q.pop();
        for(int i=head[u];i;i=Next[i]){
            int y=ver[i];
            num[y]+=num[u];
            du[y]--;
            if(!du[y])q.push(y);
        }
    }
    for(int i=1;i<=n;i++){
        printf("%d\n",num[cnt[i]]);
    }
    return 0;
}
/*
6
a
bb
aa
abaa
abaaa
abaaa
abaaabaa
*/
P5357 【模板】AC自动机(二次加强版)

 

P2444 [POI2000]病毒

·跳fail能跳到危险节点的节点,自身必为危险节点。

#include
#include
#include
#include
using namespace std;
int n,now,tot,len,flag;
char s[30010];
int tree[30010][2],ed[30010],fail[30010];
void insert(){
    now=0;
    len=strlen(s+1);
    for(int i=1;i<=len;i++){
        if(!tree[now][s[i]-'0'])tree[now][s[i]-'0']=++tot;
        now=tree[now][s[i]-'0'];
    }
    ed[now]=1;
}
queue<int>q;
void getfail(){
    for(int i=0;i<2;i++){
        fail[tree[0][i]]=0;
        if(tree[0][i]){    
            q.push(tree[0][i]);
        }
    }
    while(!q.empty()){
        int u=q.front();
        q.pop();
        for(int i=0;i<2;i++){
            int v=tree[u][i];
            if(v){
                fail[v]=tree[fail[u]][i];
                if(ed[fail[v]])ed[v]=1;
                q.push(v);
            }
            else tree[u][i]=tree[fail[u]][i];
        }
    }
}
int vis[30010];
int dfs(int x){
    if(flag)return 1;
    if(ed[x])return 0;
    if(vis[x])return 1;
    if(x)vis[x]=1;
    int val=(dfs(tree[x][0])|dfs(tree[x][1]));
    if(val)flag=1;
    vis[x]=0;
    return val;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%s",s+1);
        insert();
    }
    getfail();
    if(dfs(0))printf("TAK\n");
    else printf("NIE\n");
    return 0;
}
/*
3
011
11
00000
*/
P2444 [POI2000]病毒

 

P4052 [JSOI2007]文本生成器

·trie树里很多走不到的字母相当于回到根,转移和统计带根一起算。

#include
#include
#include
#include
using namespace std;
int n,m;
char s[110];
int now,len,tot,tree[6010][26],fail[6010],ed[6010];
void insert(){
    now=0;
    len=strlen(s+1);
    for(int i=1;i<=len;i++){
        if(!tree[now][s[i]-'A'])tree[now][s[i]-'A']=++tot;
        now=tree[now][s[i]-'A'];
    }
    ed[now]=1;
}
queue<int>q;
void getfail(){
    for(int i=0;i<26;i++){
        if(tree[0][i]){
            q.push(tree[0][i]);
        }
    }
    while(q.size()){
        int u=q.front();
        q.pop();
        for(int i=0;i<26;i++){
            int v=tree[u][i];
            if(v){
                fail[v]=tree[fail[u]][i];
                q.push(v);
                if(ed[fail[v]])ed[v]=1;
            }
            else{
                tree[u][i]=tree[fail[u]][i];
            }
        }
    }
}
int f[110][6010],ans;
void work(){
    f[0][0]=1;
    for(int t=0;t){
        for(int i=0;i<=tot;i++){
            if(ed[i])continue;
            for(int j=0;j<26;j++){
                if(ed[tree[i][j]])continue;
                f[t+1][tree[i][j]]=(f[t+1][tree[i][j]]+f[t][i])%10007;
            }
        }
    }
    for(int i=0;i<=tot;i++)ans=(ans+f[m][i])%10007;
}
int ks(int x,int k){
    int num=1;
    while(k){
        if(k&1)num=num*x%10007;
        x=x*x%10007;
        k>>=1;
    }
    return num;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%s",s+1);
        insert();
    }
    getfail();
    work();
    printf("%d\n",((ks(26,m)-ans)%10007+10007)%10007);
    return 0;
}
P4052 [JSOI2007]文本生成器

 

P2414 [NOI2011]阿狸的打字机

 ·这题具体写个题解吧

#include
#include
#include
#include
#include
using namespace std;
const int N=1e5+10;
char s[N];
int fa[N],lens,now,tree[N][26],tree1[N][26],num,ed[N],tot,m,fail[N],ans[N];
struct node{
    int x,id;
    node(int a=0,int b=0){
        x=a,id=b;
    }
};
vectorv[N];
vector<int>e[N];
void insert(){
    for(int j=1;j<=lens;j++){
        if(s[j]=='B')now=fa[now];
        else if(s[j]=='P'){
            num++;
            ed[num]=now;
            e[now].push_back(num);
        }
        else{
            if(!tree[now][s[j]-'a'])tree1[now][s[j]-'a']=tree[now][s[j]-'a']=++tot,fa[tot]=now;
            now=tree[now][s[j]-'a'];
        }
    }
}
int Next[N],head[N],tot1,ver[N];
void add(int x,int y){
    ver[++tot1]=y;
    Next[tot1]=head[x];
    head[x]=tot1;
}
queue<int>q0;
void getfail(){
    for(int i=0;i<26;i++){
        if(tree[0][i]){
            add(0,tree[0][i]);
            q0.push(tree[0][i]);
        }
    }
    while(q0.size()){
        int u=q0.front();
        q0.pop();
        for(int i=0;i<26;i++){
            int v=tree1[u][i];
            if(v){
                fail[v]=tree1[fail[u]][i];
                add(fail[v],v);
                q0.push(v);
            }
            else tree1[u][i]=tree1[fail[u]][i];
        }
    }
}
int tim,rec[N],rec1[N];
void dfs(int x){
    rec[x]=++tim;
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        dfs(y);
    }
    rec1[x]=tim;
}
int tr[N];
void add1(int x,int val){
    for(;x<=tim;x+=(x&-x))tr[x]+=val;
}
int ask(int x){
    int sum=0;
    for(;x;x-=(x&-x))sum+=tr[x];
    return sum;
}
void solve(int x){
    for(int i=0;i){
        node y=v[x][i];
        ans[y.id]=ask(rec1[ed[y.x]])-ask(rec[ed[y.x]]-1);
    }
}
void dfs1(int x){
    if(e[x].size()){
        for(int i=0;i){
            int y=e[x][i];
            solve(y);
        }
    }
    for(int i=0;i<26;i++){
        if(tree[x][i]){
            add1(rec[tree[x][i]],1);
            dfs1(tree[x][i]);
            add1(rec[tree[x][i]],-1);
        }
    }
}
int main()
{
    scanf("%s",s+1);
    lens=strlen(s+1);
    insert();
    getfail();
    dfs(0);
    scanf("%d",&m);
    for(int i=1,x,y;i<=m;i++){
        scanf("%d%d",&x,&y);
        v[y].push_back(node(x,i));
    }
    dfs1(0);
    for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
    return 0;
 } 
P2414 [NOI2011]阿狸的打字机

 

回文自动机:

(前置)P3805 【模板】manacher算法

·注意细节。

#include
#include
#include
using namespace std;
char s[23000010],a[11000010];
int len,lst=0,p[23000010],pos,ans;
void manacher(){
    for(int i=1;i<=len*2+1;i++){
        if(lst<i){
            pos=i;
            for(int j=i;j<=len*2+1&&2*i-j>0;j++){
                if(s[j]!=s[2*i-j])break;
                p[i]++;
                lst=j;
            }
        }
        else{
            int v=2*pos-i;
            if(i+p[v]-1>lst)p[i]=lst-i+1;
            else if(i+p[v]-1p[v];
            else{
                p[i]=p[v];
                while(s[i+p[i]]==s[i-p[i]])p[i]++;
                lst=i+p[i]-1;
                pos=i;
            }
        }
    }
}
int main()
{
    scanf("%s",a+1);
    len=strlen(a+1);
    s[0]=s[1]='#';
    for(int i=1;i<=len;i++){
        s[i*2]=a[i];
        s[i*2+1]='#';
    }
    manacher();
    for(int i=1;i<=2*len+1;i++)ans=max(ans,p[i]-1);
    printf("%d\n",ans);
    return 0;
}
P3805 【模板】manacher算法

 

P5496 【模板】回文自动机(PAM)

·从一个节点连出去新边新建节点的时候,处理完新节点的fail再把新节点赋给原节点的儿子指针,不然一定出现我跳我自己的死循环。(原因是撞在了边界上)

#include
#include
#include
using namespace std;
const int S=5e5+10;
char s[S];
struct PAM{
    int ch[26],len,num,fail;
}a[S];
int lst,tot=1,lens,k;
void build(){
    a[0].fail=1,a[1].fail=1;
    a[0].len=0,a[1].len=-1;
}
int getfail(int x,int y){
    while(s[y-a[x].len-1]!=s[y])x=a[x].fail;
    return x;
}
void insert(int x){
    int pos=getfail(lst,x);
    if(!a[pos].ch[s[x]-'a']){
        a[++tot].len=a[pos].len+2;
        a[tot].fail=a[getfail(a[pos].fail,x)].ch[s[x]-'a'];
        a[tot].num=a[a[tot].fail].num+1;
        a[pos].ch[s[x]-'a']=tot;//!
    }
    lst=a[pos].ch[s[x]-'a'];
}
int main()
{
    scanf("%s",s+1);
    lens=strlen(s+1);
    build();
    for(int i=1;i<=lens;i++){
        s[i]=(s[i]-97+k)%26+97;
        insert(i);
        printf("%d ",a[lst].num);
        k=a[lst].num;
    }
    return 0;
} 
P5496 【模板】回文自动机(PAM)

 

 P3649 [APIO2014]回文串

·和上面的AC自动机不同,回文自动机里的节点建立是有序的,可以将统计信息用循环加回fail。

·不开long long见祖宗,下次认真算一下…什么时候了还犯这问题。

#include
#include
#include
using namespace std;
char s[300010];
struct PAM{
    int ch[26],fail,len,cnt;
}a[300010];
int lens,lst,tot=1;
long long ans;
void build(){
    a[0].fail=a[1].fail=1;
    a[0].len=0,a[1].len=-1;
}
int getfail(int x,int y){
    while(s[y-a[x].len-1]!=s[y])x=a[x].fail;
    return x;
}
void insert(int x){
    int pos=getfail(lst,x);
    if(!a[pos].ch[s[x]-'a']){
        a[++tot].len=a[pos].len+2;
        a[tot].cnt=1;
        a[tot].fail=a[getfail(a[pos].fail,x)].ch[s[x]-'a'];
        a[pos].ch[s[x]-'a']=tot;
    }
    else a[a[pos].ch[s[x]-'a']].cnt++;
    lst=a[pos].ch[s[x]-'a'];
}
int main()
{
    scanf("%s",s+1);
    lens=strlen(s+1);
    build();
    for(int i=1;i<=lens;i++)insert(i);
    for(int i=tot;i>1;i--){
        a[a[i].fail].cnt+=a[i].cnt;
        ans=max(ans,1ll*a[i].len*a[i].cnt);
    }
    printf("%lld\n",ans);
    return 0;
}
P3649 [APIO2014]回文串

 

P4287 [SHOI2011]双倍回文

·记录一个和fail类似的指针half,表示不超过长度一半的最长后缀,减少需要跳的次数。

·从原节点的half开始寻找新节点的half,注意和新节点的len/2比较大小时要加上2,补上头尾两个字母。由此得知,当新节点的len<=2时,直接让它的half指针和fail指针相同,否则比较时出现死循环。

·困成智障就不要写题,一个+2愣是调了20min。

#include
#include
using namespace std;
int lens,lst,tot=1,ans;
char s[500010];
struct PAM{
    int len,fail,ch[26],half;
}a[500010];
void build(){
    a[0].fail=a[1].fail=1;
    a[0].half=a[1].half=1;
    a[0].len=0,a[1].len=-1;
}
int getfail(int x,int y){
    while(s[y-a[x].len-1]!=s[y])x=a[x].fail;
    return x;
}
int gethalf(int x,int y){
    while(s[y-a[x].len-1]!=s[y]||a[x].len*2+4>a[tot].len)x=a[x].fail;
    return x;
}
void insert(int x){
    int pos=getfail(lst,x);
    if(!a[pos].ch[s[x]-'a']){
        a[++tot].len=a[pos].len+2;
        a[tot].fail=a[getfail(a[pos].fail,x)].ch[s[x]-'a'];
        if(a[tot].len<=2)a[tot].half=a[tot].fail;
        else a[tot].half=a[gethalf(a[pos].half,x)].ch[s[x]-'a'];
        if((a[a[tot].half].len%2==0)&&(a[a[tot].half].len*2==a[tot].len))ans=max(ans,a[tot].len);
        a[pos].ch[s[x]-'a']=tot;
    }
    lst=a[pos].ch[s[x]-'a'];
}
int main()
{
    scanf("%d",&lens);
    scanf("%s",s+1);
    build();
    for(int i=1;i<=lens;i++){
        insert(i);
    }
    printf("%d",ans);
    return 0;
} 
P4287 [SHOI2011]双倍回文

 

后缀数组:

P3809 【模板】后缀排序

·终于彻底理解这个排序了

#include
#include
#include
using namespace std;
const int N=1e6+10;
char s[N];
int x[N],y[N],c[N],sa[N],n,m;
void getsa(){
    for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
    for(int i=2;i<=m;i++)c[i]+=c[i-1];
    for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
    for(int k=1;k<=n;k<<=1){
        int num=0;
        for(int i=n-k+1;i<=n;i++)y[++num]=i;
        for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
        for(int i=1;i<=m;i++)c[i]=0;
        for(int i=1;i<=n;i++)c[x[i]]++;
        for(int i=2;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
        swap(x,y);
        x[sa[1]]=1;
        num=1;
        for(int i=2;i<=n;i++){
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
        }
        if(num==n)break;
        m=num;
    }
}
int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    m=122;
    getsa();
    for(int i=1;i<=n;i++)printf("%d ",sa[i]);
    return 0;
}
P3809 【模板】后缀排序

 

P2852 [USACO06DEC]牛奶模式Milk Patterns

·注意细节

#include
#include
#include
using namespace std;
const int N=40010;
int n,kk,ans;
int s[N],x[N],y[N],sa[N],c[N],a[N];
int height[N],m,rk[N];
void getsa(){
    for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
    for(int i=2;i<=m;i++)c[i]+=c[i-1];
    for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
    for(int k=1;k<=n;k<<=1){
        int num=0;
        for(int i=n-k+1;i<=n;i++)y[++num]=i;
        for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
        for(int i=1;i<=m;i++)c[i]=0;
        for(int i=1;i<=n;i++)c[x[i]]++;
        for(int i=1;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
        swap(x,y);
        x[sa[1]]=1;
        num=1;
        for(int i=2;i<=n;i++){
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
        }
        if(num==n)break;
        m=num;
    }
}
void getheight(){
    for(int i=1;i<=n;i++)rk[sa[i]]=i;
    int k=0;
    for(int i=1;i<=n;i++){
        if(rk[i]==1){
            height[1]=k=0;
            continue;
        }
        if(k)k--;
        int j=sa[rk[i]-1];
        while(j+k<=n&&i+k<=n&&s[j+k]==s[i+k])k++;
        height[rk[i]]=k;
    }
}
int check(int x){
    int num=0;
    for(int i=1;i<=n;i++){
        if(height[i]>=x)num++;
        else num=0;
        if(num>=kk-1)return 1;
    }
    return 0;
}
int main()
{
    scanf("%d%d",&n,&kk);
    for(int i=1;i<=n;i++)scanf("%d",&s[i]),a[i]=s[i];
    sort(a+1,a+n+1);
    m=unique(a+1,a+n+1)-a-1;
    for(int i=1;i<=n;i++){
        s[i]=lower_bound(a+1,a+m+1,s[i])-a;
    }
    getsa();
    getheight();
    int l=0,r=n;
    while(l<=r){
        int mid=(l+r)/2;
        if(check(mid)){
            ans=mid;
            l=mid+1;
        }
        else r=mid-1;
    }
    printf("%d\n",ans);
    return 0;
}
P2852 [USACO06DEC]牛奶模式Milk Patterns

 

P4248 [AHOI2013]差异(后缀数组)

·利用lcp(i,k)=min(height(j))(i+1<=j<=k)的性质,处理出每个height能为哪一段作出贡献。

·最后单调栈内剩下的元素的右端点要记得处理。

#include
#include
#include
using namespace std;
const int N=1e6+10;
char s[N];
int n,m;
int x[N],y[N],c[N],sa[N],rk[N],height[N];
long long ans;
int stack[N],top,l[N],r[N];
void getsa(){
    for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
    for(int i=2;i<=m;i++)c[i]+=c[i-1];
    for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
    for(int k=1;k<=n;k<<=1){
        int num=0;
        for(int i=n-k+1;i<=n;i++)y[++num]=i;
        for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
        for(int i=1;i<=m;i++)c[i]=0;
        for(int i=1;i<=n;i++)c[x[i]]++;
        for(int i=1;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
        swap(x,y);
        x[sa[1]]=1;
        num=1;
        for(int i=2;i<=n;i++){
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
        }
        if(num==n)break;
        m=num;
    }
}
void getheight(){
    for(int i=1;i<=n;i++)rk[sa[i]]=i;
    int k=0;
    for(int i=1;i<=n;i++){
        if(rk[i]==1){
            height[1]=k=0;
            continue;
        }
        if(k)k--;
        int j=sa[rk[i]-1];
        while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++;
        height[rk[i]]=k;
    }
}
int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    m=122;
    getsa();
    getheight();
    ans=1ll*(n-1)*n*(n+1)/2;
    for(int i=1;i<=n;i++){
        while(top&&height[stack[top]]>=height[i])r[stack[top]]=i,top--;
        l[i]=stack[top];
        stack[++top]=i;
    }
    while(top){
        r[stack[top]]=n+1;
        top--;
    }
    for(int i=1;i<=n;i++){
        ans-=2ll*(i-l[i])*(r[i]-i)*height[i];
    }
    printf("%lld\n",ans);
    return 0;
 } 
P4248 [AHOI2013]差异(后缀数组)

 

 后缀自动机:

P3804 【模板】后缀自动机

·后缀链接形成一棵树。树上从叶子到根累计信息,dfs。

#include
#include
#include
using namespace std;
const int N=1e6+10;
struct SAM{
    int link,len,cnt;
    int ch[26];
}a[2*N];
int lst,siz,n;
long long ans;
char s[N];
int head[2*N],Next[2*N],tot,ver[2*N];
void build(){
    a[0].link=-1;
    a[0].len=0;
    siz=1,lst=0;
}
void add(int x,int y){
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
}
void insert(char c){
    int cur=++siz;
    a[cur].cnt=1;
    a[cur].len=a[lst].len+1;
    int p=lst;
    while(p!=-1&&!a[p].ch[c-'a']){
        a[p].ch[c-'a']=cur;
        p=a[p].link;
    }
    if(p==-1){
        a[cur].link=0;
    }
    else{
        int q=a[p].ch[c-'a'];
        if(a[p].len+1==a[q].len){
            a[cur].link=q;
        }
        else{
            int clone=++siz;
            a[clone].len=a[p].len+1;
            a[clone].link=a[q].link;
            for(int i=0;i<26;i++)a[clone].ch[i]=a[q].ch[i];
            while(p!=-1&&a[p].ch[c-'a']==q){
                a[p].ch[c-'a']=clone;
                p=a[p].link;
            }
            a[q].link=a[cur].link=clone;
        }
    }
    lst=cur;
}
void dfs(int x){
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        dfs(y);
        a[x].cnt+=a[y].cnt;
    }
    if(a[x].cnt>1)ans=max(ans,1ll*a[x].cnt*a[x].len);
}
int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    build();
    for(int i=1;i<=n;i++){
        insert(s[i]);
    }
    for(int i=1;i<=siz;i++){
        add(a[i].link,i);
    }
    dfs(0);
    printf("%lld\n",ans);
    return 0;
}
P3804 【模板】后缀自动机

 

 P4070 [SDOI2016]生成魔咒

·后缀自动机的状态数上限为2n-1,结构体开两倍。(转移数的上限为3n-4。)

·统计子串数有两种方式,dp整个自动机是其中一种,这里询问是O(n)级别所以不合适。另一种是计算所有状态对应的子串数之和,一个状态v的子串数=len(v)-len(link(v)),len为状态统计的长度len也即此状态对应的最长子串长度。由后缀链接的定义可知,每个状态对应长度连续的一组子串且短串为长串后缀,而后缀链接到的状态包括长度由len(v)到minlen(v)第一个不符合相同结束点集合的子串。设v后缀链接到的状态为z,len(z)+1=minlen(v)。由于一个状态对应的子串长度连续且到len(z)这个长度就不属于当前状态,当前状态的子串个数就是len(v)-len(z)了。

·为使转移合法而拆开状态时,子串的个数是不变的。只需要在加入新的状态并找到link之后维护答案即可。

·字符集较大,使用map。(我还离了个完全没有必要的散) 

#include
#include
#include
#include
using namespace std;
const int N=1e5+10;
int n,m,s[N],b[N],lst,siz;
long long ans;
struct SAM{
    int link,len;
    map<int,int>mp;
}a[2*N];
void build(){
    a[0].link=-1;
    a[0].len=0;
    lst=0,siz=1;
}
void insert(int c){
    int cur=++siz;
    a[cur].len=a[lst].len+1;
    int p=lst;
    while(p!=-1&&!a[p].mp.count(c)){
        a[p].mp[c]=cur;
        p=a[p].link;
    }
    if(p==-1){
        a[cur].link=0;
        ans+=a[cur].len;
    }
    else{
        int q=a[p].mp[c];
        if(a[p].len+1==a[q].len){
            a[cur].link=q;
            ans+=a[cur].len-a[q].len;
        }
        else{
            int clone=++siz;
            a[clone].len=a[p].len+1;
            a[clone].link=a[q].link;
            a[clone].mp=a[q].mp;
            while(p!=-1&&a[p].mp[c]==q){
                a[p].mp[c]=clone;
                p=a[p].link;
            }
            a[q].link=a[cur].link=clone;
            ans+=a[cur].len-a[clone].len;
        }
    }
    lst=cur;
}
int main()
{
    scanf("%d",&n);
    build();
    for(int i=1;i<=n;i++){
        scanf("%d",&s[i]);
        b[i]=s[i];
    }
    sort(b+1,b+n+1);
    m=unique(b+1,b+n+1)-b-1;
    for(int i=1;i<=n;i++){
        s[i]=lower_bound(b+1,b+m+1,s[i])-b;
        insert(s[i]);
        printf("%lld\n",ans);
    }
    return 0;
}
P4070 [SDOI2016]生成魔咒

 

P3975 [TJOI2015]弦论

·dp求每个点对应路径数量的时候,注意不要重复计算一个点。

·t=1时建出link树求每个状态出现次数,dp时累计进去。t=0时让每个状态的出现次数都为1,不要忘记处理。

#include
#include
#include
using namespace std;
const int N=5e5+10;
char s[N],ans[N];
int t,k,n,siz,lst,dep;
struct SAM{
    int len,link,cnt,num;
    int ch[26];
}a[N*2];
void build(){
    a[0].len=0;
    a[0].link=-1;
    siz=0,lst=0;
}
int head[2*N],Next[2*N],tot,ver[2*N];
void add(int x,int y){
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
}
void insert(char c){
    int cur=++siz;
    a[cur].cnt=1;
    a[cur].len=a[lst].len+1;
    int p=lst;
    while(p!=-1&&!a[p].ch[c-'a']){
        a[p].ch[c-'a']=cur;
        p=a[p].link;
    }
    if(p==-1){
        a[cur].link=0;
    }
    else{
        int q=a[p].ch[c-'a'];
        if(a[p].len+1==a[q].len){
            a[cur].link=q;
        }
        else{
            int clone=++siz;
            a[clone].len=a[p].len+1;
            a[clone].link=a[q].link;
            for(int i=0;i<26;i++)a[clone].ch[i]=a[q].ch[i];
            while(p!=-1&&a[p].ch[c-'a']==q){
                a[p].ch[c-'a']=clone;
                p=a[p].link; 
            }
            a[cur].link=a[q].link=clone;
        }
    }
    lst=cur;
}
void dfs(int x){
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        dfs(y);
        a[x].cnt+=a[y].cnt;
    }
}
void dfs1(int x){
    a[x].num=a[x].cnt;
    for(int i=0;i<26;i++){
        if(a[x].ch[i]){
            if(!a[a[x].ch[i]].num)dfs1(a[x].ch[i]);
            a[x].num+=a[a[x].ch[i]].num;
        }
    }
}
void dfs2(int x,int sum){
    if(x&&a[x].cnt>=sum){
        return;
    }
    if(x)sum-=a[x].cnt;
    for(int i=0;i<26;i++){
        if(a[a[x].ch[i]].num>=sum){
            ans[++dep]='a'+i;
            dfs2(a[x].ch[i],sum);
            return;
        }
        else sum-=a[a[x].ch[i]].num;
    }
}
int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    scanf("%d%d",&t,&k);
    build();
    for(int i=1;i<=n;i++){
        insert(s[i]);
    }
    if(t==1){
        for(int i=1;i<=siz;i++){
            add(a[i].link,i);
        }
        dfs(0);
        a[0].cnt=0;
    }
    else{
        for(int i=1;i<=siz;i++){
            a[i].cnt=1;
        }
    }
    dfs1(0);
    if(a[0].num<k){
        printf("-1\n");
        return 0;
    }
    a[0].num=0;
    dfs2(0,k);
    for(int i=1;i<=dep;i++){
        printf("%c",ans[i]);
    }
    return 0;
}
P3975 [TJOI2015]弦论

 

P4248 [AHOI2013]差异(后缀自动机)

·灵活运用反转,前缀不好处理就转化成后缀。求后缀的公共前缀=反转以后求前缀的公共后缀。两个前缀的公共后缀=link树上的lca。

·统计路径经过哪些点处理贡献=考虑每条边被多少路径经过。

#include
#include
#include
using namespace std;
const int N=1e6+10;
int n,lst,siz;
long long ans;
char s[N];
struct SAM{
    int link,cnt,len;
    int ch[26];
}a[N];
void build(){
    a[0].len=0;
    a[0].link=-1;
    lst=0,siz=0;
}
int ver[N],head[N],Next[N],tot,edge[N];
void add(int x,int y,int z){
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
    edge[tot]=z;
}
void insert(char c){
    int cur=++siz;
    a[cur].len=a[lst].len+1;
    a[cur].cnt=1;
    int p=lst;
    while(p!=-1&&!a[p].ch[c-'a']){
        a[p].ch[c-'a']=cur;
        p=a[p].link;
    }
    if(p==-1){
        a[cur].link=0;
    }
    else{
        int q=a[p].ch[c-'a'];
        if(a[p].len+1==a[q].len){
            a[cur].link=q;
        }
        else{
            int clone=++siz;
            a[clone].link=a[q].link;
            a[clone].len=a[p].len+1;
            for(int i=0;i<26;i++)a[clone].ch[i]=a[q].ch[i];
            while(p!=-1&&a[p].ch[c-'a']==q){
                a[p].ch[c-'a']=clone;
                p=a[p].link;
            }
            a[q].link=a[cur].link=clone;
        }
    }
    lst=cur;
}
int si[N];
void dfs(int x,int lon){
    si[x]=a[x].cnt;
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        dfs(y,edge[i]);
        si[x]+=si[y];
    }
    ans+=1ll*si[x]*(n-si[x])*lon;
}
int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    for(int i=1;i<=n/2;i++){
        swap(s[i],s[n-i+1]);
    } 
    build();
    for(int i=1;i<=n;i++){
        insert(s[i]);
    }
    for(int i=1;i<=siz;i++){
        add(a[i].link,i,a[i].len-a[a[i].link].len);
    }
    dfs(0,0);
    printf("%lld\n",ans);
    return 0;
 } 
P4248 [AHOI2013]差异(后缀自动机)

 

P3346 [ZJOI2015]诸神眷顾的幻想乡

·所有叶子节点两两之间的路径覆盖一棵树的所有子串:度为1的就是叶节点,以每个叶节点开始dfs一遍树构建广义SAM。

·广义SAM(在线)的正确写法,insert函数要比普通SAM多两个特判:

  1.进入insert函数以后,判断a[lst].ch[c]&&a[lst].len+1==a[a[lst].ch[c]].len,即是否存在和要新建的状态完全等价的状态,避免重复建点。

  2.新状态的link不指向0,且指向的是需要拆出来的状态时,判断a[p(last跳过link以后找到的状态)].len+1==a[cur(当前状态)].len,即拆出来的状态是不是和当前状态完全等价。如果等价,此时新节点为空节点,不承载任何独特信息,让last等于拆出来的状态。

·这是广义SAM的在线写法,离线可以建出trie树然后bfs建立SAM,dfs可能被卡成n2。离线不考虑上面的特判。

·注意细节,每次都要把last放在正确位置,以及while里不要忘记不断跳link。

#include
#include
using namespace std;
const int N=1e5+10;
int n,color,col[N];
int ver[2*N],Next[2*N],head[N],tot,du[N];
long long ans;
int lst,si;
struct SAM{
    int len,link;
    int ch[10];
}a[30*N];
void build(){
    a[0].link=-1;
}
void add(int x,int y){
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
}
void insert(int c){
    if(a[lst].ch[c]&&a[lst].len+1==a[a[lst].ch[c]].len){
        lst=a[lst].ch[c];
        return;
    }
    int cur=++si,flag=0,clone;
    a[cur].len=a[lst].len+1;
    int p=lst;
    while(p!=-1&&!a[p].ch[c]){
        a[p].ch[c]=cur;
        p=a[p].link;
    }
    if(p==-1){
        a[cur].link=0;
    }
    else{
        int q=a[p].ch[c];
        if(a[p].len+1==a[q].len){
            a[cur].link=q;
        }
        else{
            if(a[p].len+1==a[cur].len)flag=1;
            clone=++si;
            a[clone].len=a[p].len+1;
            a[clone].link=a[q].link;
            for(int i=0;ia[q].ch[i];
            while(p!=-1&&a[p].ch[c]==q){
                a[p].ch[c]=clone;
                p=a[p].link;
            }
            a[q].link=a[cur].link=clone;
        }
    }
    lst=(flag?clone:cur);
}
void dfs1(int x,int fa){
    insert(col[x]);
    int now=lst;
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        if(y==fa)continue;
        dfs1(y,x);
        lst=now;
    }
}
int main()
{
    scanf("%d%d",&n,&color);
    for(int i=1;i<=n;i++){
        scanf("%d",&col[i]);
    }
    for(int i=1,x,y;i){
        scanf("%d%d",&x,&y);
        add(x,y),add(y,x);
        du[x]++,du[y]++;
    }
    build();
    for(int i=1;i<=n;i++){
        if(du[i]==1){
            lst=0;
            dfs1(i,0);
        }
    }
    for(int i=1;i<=si;i++)ans+=a[i].len-a[a[i].link].len;
    printf("%lld\n",ans);
    return 0;
 } 
P3346 [ZJOI2015]诸神眷顾的幻想乡

 

刚刚忘记留空行了(´;ω;`)编辑不能

持续补完

(给博客园的延迟烧个高香。)

P2852 [USACO06DEC]牛奶模式Milk Patterns

你可能感兴趣的:(字符串做题笔记)