字符串做题笔记

 

AC自动机:

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

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

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

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
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<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
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<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
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<m;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<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
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;
    }
};
vector<node>v[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<v[x].size();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<e[x].size();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]阿狸的打字机

 

回文自动机:

(manacher)P3805 【模板】manacher算法

·注意细节。

#include<iostream>
#include<cstdio>
#include<cstring>
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]-1<lst)p[i]=p[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算法

 

(manacher)P4555 [国家集训队]最长双回文串

·注意细节,题目要求两回文串合起来才满足条件,那么边界就是不满足的。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=2e5+10;
char s[N],s0[N];
int p[N],lens,l[N],r[N],ans;
void manacher()
{
    p[0]=1;
    int pos=0,right=0;
    for(int i=1;i<=lens;i++){
        if(right<i){
            p[i]=1;
            while(s[i-p[i]]==s[i+p[i]]&&i-p[i]>=0&&i+p[i]<=lens)p[i]++;
            right=(i+p[i]-1);
            pos=i;
            l[right]=max(l[right],p[i]-1);
            r[i-p[i]+1]=max(r[i-p[i]+1],p[i]-1);
        }
        else{
            int j=2*pos-i;
            if(i+p[j]-1<right)p[i]=p[j];
            else if(i+p[j]-1>right)p[i]=right-i+1;
            else{
                p[i]=p[j];
                while(s[i-p[i]]==s[i+p[i]]&&i-p[i]>=0&&i+p[i]<=lens)p[i]++;
                right=(i+p[i]-1);
                pos=i;
            }
            l[i+p[i]-1]=max(l[i+p[i]-1],p[i]-1);
            r[i-p[i]+1]=max(r[i-p[i]+1],p[i]-1);
        }
    }
}
int main()
{
    scanf("%s",s0+1);
    int len=strlen(s0+1);
    lens=len*2;
    for(int i=1;i<=len;i++){
        s[i*2-1]=s0[i];
        s[i*2]='#';
    }
    s[0]='#';
    manacher();
    for(int i=2;i<=lens;i+=2){
        r[i]=max(r[i],r[i-2]-2);
    }
    for(int i=lens;i>=1;i-=2){
        l[i]=max(l[i],l[i+2]-2);
    }
    for(int i=2;i<=lens;i+=2){
        if(l[i]&&r[i])ans=max(ans,l[i]+r[i]);
    }
    printf("%d\n",ans);
    return 0;
}
P4555 [国家集训队]最长双回文串

 

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

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

#include<iostream>
#include<cstdio>
#include<cstring>
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<iostream>
#include<cstdio>
#include<cstring>
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<iostream>
#include<cstdio>
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<iostream>
#include<cstdio>
#include<cstring>
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<iostream>
#include<cstdio>
#include<algorithm>
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<iostream>
#include<cstdio>
#include<cstring>
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]差异(后缀数组)

 

P2463 [SDOI2008]Sandy的卡片

·check里注意符合条件的区间边界

·注意细节

#include<iostream>
#include<cstdio>
using namespace std;
const int N=4e5+10;
int n,l,r,m,lst,ans,vis[N],t[N],top,sum,tim=3866;
int s[N],cnt,x[N],y[N],c[N],sa[N],height[N],rk[N],id[N],rec[N];
void getsa(){
    for(int i=1;i<=cnt;i++)c[x[i]=s[i]]++;
    for(int i=2;i<=m;i++)c[i]+=c[i-1];
    for(int i=cnt;i>=1;i--)sa[c[x[i]]--]=i;
    for(int k=1;k<=cnt;k<<=1){
        int num=0;
        for(int i=cnt-k+1;i<=cnt;i++)y[++num]=i;
        for(int i=1;i<=cnt;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<=cnt;i++)c[x[i]]++;
        for(int i=1;i<=m;i++)c[i]+=c[i-1];
        for(int i=cnt;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<=cnt;i++){
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
        }
        if(num==cnt)break;
        m=num;
    }
}
void getheight(){
    for(int i=1;i<=cnt;i++)rk[sa[i]]=i;
    int k=0;
    for(int i=1;i<=cnt;i++){
        if(rk[i]==1){
            height[1]=0;
            k=0;
            id[1]=rec[i];
            continue;
        }
        if(k)k--;
        int j=sa[rk[i]-1];
        while(s[i+k]==s[j+k]&&i+k<=cnt&&j+k<=cnt)k++;
        height[rk[i]]=k;
        id[rk[i]]=rec[i];
    }
}
int check(int x){
    while(top)vis[t[top--]]=0;
    sum=0;
    for(int i=1;i<=cnt;i++){
        if(height[i]<x){
            while(top)vis[t[top--]]=0;
            sum=0;
        }
        else{
            if(!top){
                vis[id[i-1]]=1;
                t[++top]=id[i-1];
                sum++;
                if(sum==n)return 1;
            }
            t[++top]=id[i];
            if(!vis[t[top]]){
                vis[t[top]]=1;
                sum++;
                if(sum==n)return 1;
            }
        }
    }
    return 0;
}
int main()
{
    scanf("%d",&n);
    r=N;
    for(int i=1,a;i<=n;i++){
        scanf("%d",&a);
        r=min(r,a-1);
        for(int j=1,b;j<=a;j++){
            scanf("%d",&b);
            if(j==1){
                lst=b;
                continue;
            }
            s[++cnt]=b-lst+2000;
            rec[cnt]=i;
            lst=b;
        }
        s[++cnt]=++tim;
        rec[cnt]=i;
    }
    m=tim;
    getsa();
    getheight();
    l=1;
    while(l<=r){
        int mid=(l+r)/2;
        if(check(mid)){
            ans=mid;
            l=mid+1;
        }
        else r=mid-1;
    }
    printf("%d\n",ans+1);
    return 0;
}
//sa[c[x[y[i]]]--]=y[i]而不是=i,sa记录的是位置, y记录第二关键字对应的第一关键字的位置,i是排名
//check之前清空记录的数组
//二分注意细节 
P2463 [SDOI2008]Sandy的卡片

 

P5546 [POI2000]公共串

·和上一题其实一个题意,写了二分check的另一种方法

·注意作为分隔符的数字要互不相同,否则至少产生1的贡献(或者不把它们打上属于某个串的标记)。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=2e5+10;
int tim,s[N],n,lst=0,x[N],y[N],c[N],m,sa[N],height[N],rk[N],rec[N],rea[N],l,r,ans;
char s0[N];
void getsa(){
    for(int i=1;i<=lst;i++)c[x[i]=s[i]]++;
    for(int i=1;i<=m;i++)c[i]+=c[i-1];
    for(int i=lst;i>=1;i--)sa[c[x[i]]--]=i;
    for(int k=1;k<=lst;k<<=1){
        int num=0;
        for(int i=lst-k+1;i<=lst;i++)y[++num]=i;
        for(int i=1;i<=lst;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<=lst;i++)c[x[i]]++;
        for(int i=1;i<=m;i++)c[i]+=c[i-1];
        for(int i=lst;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<=lst;i++){
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
        }
        if(num==lst)break;
        m=num;
    }
}
void getheight(){
    for(int i=1;i<=lst;i++)rk[sa[i]]=i;
    int k=0;
    for(int i=1;i<=lst;i++){
        if(rk[i]==1){
            height[1]=k=0;
            rea[1]=rec[i];
            continue;
        }
        if(k)k--;
        int j=sa[rk[i]-1];
        while(j+k<=lst&&i+k<=lst&&s[i+k]==s[j+k])k++;
        height[rk[i]]=k;
        rea[rk[i]]=rec[i];
    }
}
int t[N],top,vis[N];
int check(int x){
    while(top)vis[t[top--]]=0;
    int sum=0;
    for(int i=1;i<=lst;i++){
        if(height[i]<x){
            while(top)vis[t[top--]]=0;
            sum=0;
        }
        if(!vis[rea[i]]){
            vis[rea[i]]=1;
            t[++top]=rea[i];
            sum++;
            if(sum==n)return 1;
        }
    }
    return 0;
}
int main(){
    scanf("%d",&n);
    r=N;
    tim=30;
    for(int i=1;i<=n;i++){
        scanf("%s",s0+1);
        int lens=strlen(s0+1);
        r=min(r,lens);
        for(int j=1;j<=lens;j++){
            s[++lst]=s0[j]-'a'+1;
            rec[lst]=i;
        }
        s[++lst]=++tim;
        rec[lst]=i;
    }
    m=tim;
    getsa();
    getheight();
    l=0;
    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;
}
//注意作为分隔符的字符要互不相同,否则至少会被记为1的答案 
P5546 [POI2000]公共串

 

P4094 [HEOI2016/TJOI2016]字符串

·预处理log2,不然复杂度是nlog^3n

·st表总层数不用预处理出来的log数组确定而是循环确定的话会多一层,st数组的层数再开大1

·二分的时候注意边界。

·关于答案的二分注意最长不超过min(a->b,c->d)。

·内部确定区间左右边界的二分查询最小值时要让左端点加一(height数组存的是与前一个的lcp)。

·主席树查询的时候注意要确定是否出现在区间内的点是从a->b-x+1的,因为要保证当前验证的答案长度x符合是a->b的字串。

·注意主席树的数组要开大一些。可以不用预处理T[0]。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e5+10;
int n,m,lim,ans;
char s[N];
int x[N],y[N],c[N],sa[N],height[N],rk[N],tot,log[N];
int st[N][17],T[N],L[1800010],R[1800010],cnt[1800010];
void getsa(){
    for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
    for(int i=1;i<=lim;i++)c[i]+=c[i-1];
    for(int i=1;i<=n;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<=lim;i++)c[i]=0;
        for(int i=1;i<=n;i++)c[x[i]]++;
        for(int i=1;i<=lim;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]]=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(n==num)break;
        lim=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;
    }
}
void add(int &p,int pre,int l,int r,int pos,int val){
    p=++tot;
    L[p]=L[pre],R[p]=R[pre],cnt[p]=cnt[pre]+1;
    if(l==r)return;
    int mid=(l+r)/2;
    if(pos<=mid)add(L[p],L[pre],l,mid,pos,val);
    else add(R[p],R[pre],mid+1,r,pos,val);
}
int query(int p,int pre,int l,int r,int ll,int rr){
    if(ll<=l&&r<=rr){
        return cnt[p]-cnt[pre];
    }
    int mid=(l+r)/2;
    if(rr<=mid)return query(L[p],L[pre],l,mid,ll,rr);
    else if(ll>mid)return query(R[p],R[pre],mid+1,r,ll,rr);
    else return query(L[p],L[pre],l,mid,ll,mid)+query(R[p],R[pre],mid+1,r,mid+1,rr);
}
void work(){
//    memset(st,0x3f3f3f3f,sizeof(st));
    for(int i=1;i<=n;i++)st[i][0]=height[i];
    st[1][0]=N; 
    int maxn,sum;
    for(int i=1;i<=log[n];i++){
        int k=(1<<(i-1));
        for(int j=1;j<=n-k;j++){
            st[j][i]=min(st[j][i-1],st[j+k][i-1]);
        }
    }
}
int cal(int lon,int l,int r){
    int sum=r-l+1;
    int val=min(st[l][log[sum]],st[r-(1<<log[sum])+1][log[sum]]);
    return val>=lon;
}
int check(int x,int a,int b,int c,int d){
    int now=rk[c];
    int l,r,ll=now,rr=now;
    l=1,r=now-1;
    while(l<=r){
        int mid=(l+r)/2;
        if(cal(x,mid+1,now)){
            ll=mid;
            r=mid-1;
        }
        else l=mid+1;
    }
    l=now+1,r=n;
    while(l<=r){
        int mid=(l+r)/2;
        if(cal(x,now+1,mid)){
            rr=mid;
            l=mid+1;
        }
        else r=mid-1;
    }
    return query(T[rr],T[ll-1],1,n,a,b-x+1);
}
int main(){
    scanf("%d%d",&n,&m);
    scanf("%s",s+1);
    int x=1,y=0;
    log[1]=0;
    for(int i=2;i<=n;i++){
        if(i==(x<<1)){
            x=i;
            y++;
        }
        log[i]=y;
    }
    lim=122;
    getsa();
    getheight();
    for(int i=1;i<=n;i++){
        add(T[i],T[i-1],1,n,sa[i],1);
    }
    work();
    for(int i=1,a,b,c,d;i<=m;i++){
        scanf("%d%d%d%d",&a,&b,&c,&d);
        int l=1,r=min(d-c+1,b-a+1);
        ans=0;
        while(l<=r){
            int mid=(l+r)/2;
            if(check(mid,a,b,c,d)){
                ans=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        printf("%d\n",ans);
    }
    return 0;
}
//预处理log2,不然复杂度是nlog^3n
//st表总层数不用预处理出来的log数组确定而是循环确定的话会多一层,st数组的层数再开大1
//二分的时候注意边界。
//关于答案的二分注意最长不超过min(a->b,c->d)。
//内部确定区间左右边界的二分查询最小值时要让左端点加一(height数组存的是与前一个的lcp)。
//主席树查询的时候注意要确定是否出现在区间内的点是从a->b-x+1的,因为要保证当前验证的答案长度x符合是a->b的字串。 
//注意主席树的数组要开大一些。可以不用预处理T[0]。 
P4094 [HEOI2016/TJOI2016]字符串

 

P2178 [NOI2015]品酒大会

·看数据范围…把初值设置到合适的大小,至少1e15肯定不行。

#include<iostream>
#include<cstdio>
#include<vector>
#define ll long long
using namespace std;
const int N=6e5+10;
const long long inf=1e18;
ll n,m,x[N],y[N],c[N],sa[N],rk[N],height[N],fa[N];
ll a[N],ans1[N],ans=-inf,val1,ans2[N];
char s[N];
struct node{
    ll maxx,minn;
    ll num,siz;
}b[N];
vector<ll>v[N];
void getsa(){
    for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
    for(int i=1;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]]=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(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])k++;
        height[rk[i]]=k;
    }
}
int get(int x){
    if(x==fa[x])return x;
    else return fa[x]=get(fa[x]);
}
void work(int x,int y){
//    if(b[x].siz<b[y].siz)swap(x,y);
    fa[y]=x;
    ll val=max(max(b[x].num,b[y].num),max(b[x].maxx*b[y].maxx,b[x].minn*b[y].minn));
    b[x].num=max(b[x].num,val);
    ans=max(ans,val);
    ll si=b[x].siz*b[y].siz;
    b[x].siz=b[x].siz+b[y].siz;
    val1+=si;
    b[x].maxx=max(b[x].maxx,b[y].maxx);
    b[x].minn=min(b[x].minn,b[y].minn);
}
int main()
{
    scanf("%lld",&n);
    scanf("%s",s+1);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
    }
    m=122;
    getsa();
    getheight();
    for(int i=1;i<=n;i++){
        b[i].maxx=b[i].minn=a[sa[i]];
        b[i].siz=1;
        b[i].num=-inf;
        fa[i]=i;
    }
    for(int i=1;i<=n;i++)v[height[i]].push_back(i);
    for(int i=n-1;i>=0;i--){
        for(int j=0;j<v[i].size();j++){
            int x=v[i][j];
            work(get(x),get(x-1));
        }
        if(val1){
            ans1[i]=val1;
            ans2[i]=ans;
        }
    }
    for(int i=0;i<n;i++){
        printf("%lld %lld\n",ans1[i],ans2[i]);
    }
    return 0;
 } 
P2178 [NOI2015]品酒大会

 

 后缀自动机:

P3804 【模板】后缀自动机

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

#include<iostream>
#include<cstdio>
#include<cstring>
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<iostream>
#include<cstdio>
#include<map>
#include<algorithm>
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<iostream>
#include<cstdio>
#include<cstring>
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<iostream>
#include<cstdio>
#include<cstring>
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<iostream>
#include<cstdio>
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;i<color;i++)a[clone].ch[i]=a[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<n;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]诸神眷顾的幻想乡

 

P2336 [SCOI2012]喵星球上的点名

·注意莫队里面指针变化的细节

·莫队是先按左端点排序分块再在块内部按右端点排序的,我写了个假莫队…

#include<iostream>
#include<cstdio>
#include<map>
#include<algorithm>
#include<vector>
using namespace std;
const int N=5e4+10,M=1e5+10;
int n,m,siz,lst,b[M],lens;
int head[M],Next[M],tot,ver[M],rec[M],rec1[M],tim,cnt,rea[M];
int ans[M],ans1,sum[M],liv[M],fir[M];
struct node{
    int len,link;
    map<int,int>mp;
    vector<int>v;
}a[M*2];
struct que{
    int l,r,id;
}e[M];
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,int id){
    if(a[lst].mp.count(c)&&a[a[lst].mp[c]].len==a[lst].len+1){
        lst=a[lst].mp[c];
        a[lst].v.push_back(id);
        return;
    }
    int cur=++siz;
    a[cur].len=a[lst].len+1;
    a[cur].v.push_back(id);
    int p=lst,flag=0,clone;
    while(p!=-1&&!a[p].mp.count(c)){
        a[p].mp[c]=cur;
        p=a[p].link;
    }
    if(p==-1){
        a[cur].link=0;
    }
    else{
        int q=a[p].mp[c];
        if(a[p].len+1==a[q].len){
            a[cur].link=q;
        }
        else{
            clone=++siz;
            a[clone].len=a[p].len+1;
            a[clone].mp=a[q].mp;
            a[clone].link=a[q].link;
            if(p==lst)flag=1;
            while(p!=-1&&a[p].mp[c]==q){
                a[p].mp[c]=clone;
                p=a[p].link;
            }
            a[cur].link=a[q].link=clone;
        }
    }
    lst=(flag?clone:cur);
}
void dfs(int x){
    rec[x]=++tim;
    rea[tim]=x;
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        dfs(y);
    }
    rec1[x]=tim;
} 
void find(int num){
    int now=0;
    for(int i=1;i<=lens;i++){
        if(a[now].mp.count(b[i]))now=a[now].mp[b[i]];
        else return;
    }
    e[++cnt].l=rec[now],e[cnt].r=rec1[now],e[cnt].id=num;
}
void putin(int x,int t){
    if(sum[x]==0){
        ans1++;
        fir[x]=t;
    }
    sum[x]++;
}
void del(int x,int t){
    sum[x]--;
    if(sum[x]==0){
        ans1--;
        liv[x]+=t-fir[x];
    }
}    
bool cmp(que a,que b){
    return (a.l==b.l)?(a.r<b.r):(a.l<b.l);
}
int main()
{
    scanf("%d%d",&n,&m);
    build();
    for(int i=1,x,y;i<=n;i++){
        lst=0;
        scanf("%d",&x);
        for(int j=1;j<=x;j++){
            scanf("%d",&y);
            insert(y,i);
        }
        lst=0;
        scanf("%d",&x);
        for(int j=1;j<=x;j++){
            scanf("%d",&y);
            insert(y,i);
        }
    }
    for(int i=1;i<=siz;i++)add(a[i].link,i);
    dfs(0);
    for(int i=1;i<=m;i++){
        scanf("%d",&lens);
        for(int j=1;j<=lens;j++)scanf("%d",&b[j]);
        find(i);
    }
    sort(e+1,e+cnt+1,cmp);
    for(int i=e[1].l;i<=e[1].r;i++){
        for(int j=0;j<a[rea[i]].v.size();j++){
            int x=a[rea[i]].v[j];
            putin(x,1);
        }
    }
    int l=e[1].l,r=e[1].r;
    ans[e[1].id]=ans1;
    for(int i=2;i<=cnt;i++){
        int ll=e[i].l,rr=e[i].r;
        while(l<ll){
            for(int j=0;j<a[rea[l]].v.size();j++){
                int x=a[rea[l]].v[j];
                del(x,i);
            }
            l++;
        }
        while(l>ll){
            l--;
            for(int j=0;j<a[rea[l]].v.size();j++){
                int x=a[rea[l]].v[j];
                putin(x,i);
            }
        }
        while(r<rr){
            r++;
            for(int j=0;j<a[rea[r]].v.size();j++){
                int x=a[rea[r]].v[j];
                putin(x,i);
            }
        }
        while(r>rr){
            for(int j=0;j<a[rea[r]].v.size();j++){
                int x=a[rea[r]].v[j];
                del(x,i);
            }
            r--;
        }
        ans[e[i].id]=ans1;
    }
    for(int i=l;i<=r;i++){
        for(int j=0;j<a[rea[i]].v.size();j++){
            int x=a[rea[i]].v[j];
            del(x,cnt+1);
        }
    }
    for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
    for(int i=1;i<=n;i++)printf("%d ",liv[i]);
    return 0;
 } 
P2336 [SCOI2012]喵星球上的点名

 

SP1812 LCS2 - Longest Common Substring II

·和上面的Sandy以及公共串一样是求n个串中的最长公共字串,后缀自动机的做法要注意各种标记(min&max)进行比较的顺序。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=3e5+10;
int siz,lst,lens,num=1,first,ans,maxx[N];
char s[N];
struct node{
    int len,link,minn;
    int ch[26];
}a[N];
int ver[N],head[N],tot,Next[N];
void add(int x,int y){
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
}
void build(){
    a[0].link=-1;
}
void insert(char c){
    int cur=++siz;
    a[cur].len=a[lst].len+1;
    a[cur].minn=N;
    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;
            a[clone].minn=N;
            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 change(){
    int now=0,l=0;
    for(int i=1;i<=lens;i++){
        while(!a[now].ch[s[i]-'a']&&now!=0){
            now=a[now].link;
            l=min(l,a[now].len);
        }
        if(!now)l=0;
        if(a[now].ch[s[i]-'a']){
            now=a[now].ch[s[i]-'a'];
            l++;
            l=min(l,a[now].len);
            maxx[now]=max(maxx[now],l);
        }
    }
}        
void dfs(int x){
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        dfs(y);
        maxx[x]=max(maxx[x],maxx[y]);
    }
    a[x].minn=min(a[x].minn,min(maxx[x],a[x].len));
}
void dfs1(int x){
    ans=max(ans,a[x].minn);
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        dfs1(y);
    }
}
int main(){
    scanf("%s",s+1);
    build();
    lens=strlen(s+1);
    first=lens;
    for(int i=1;i<=lens;i++){
        insert(s[i]);
    }
    for(int i=1;i<=siz;i++)add(a[i].link,i);
    while(scanf("%s",s+1)!=EOF){
        memset(maxx,0,sizeof(maxx));
        num++;
        lens=strlen(s+1);
        change();
        dfs(0);
    }
    if(num==1)printf("%d\n",first);
    else{
        dfs1(0);
        printf("%d\n",ans);
    }
    return 0;
}
SP1812 LCS2 - Longest Common Substring II

附带Sandy的卡片的后缀自动机版本

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
const int N=3e5+10;
int fro,n,siz,lst,lens,num=1,first,ans,maxx[N];
int s[N];
struct node{
    int len,link,minn;
    map<int,int>mp;
}a[N];
int ver[N],head[N],tot,Next[N];
void add(int x,int y){
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
}
void build(){
    a[0].link=-1;
}
void insert(int c){
    int cur=++siz;
    a[cur].len=a[lst].len+1;
    a[cur].minn=N;
    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;
    }
    else{
        int q=a[p].mp[c];
        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;
            a[clone].minn=N;
            a[clone].mp=a[q].mp;
            while(p!=-1&&a[p].mp[c]==q){
                a[p].mp[c]=clone;
                p=a[p].link;
            }
            a[cur].link=a[q].link=clone;
        }
    }
    lst=cur;
}
void change(){
    int now=0,l=0;
    for(int i=1;i<=lens;i++){
        while(!a[now].mp.count(s[i])&&now!=0){
            now=a[now].link;
            l=min(l,a[now].len);
        }
        if(!now)l=0;
        if(a[now].mp.count(s[i])){
            now=a[now].mp[s[i]];
            l++;
            l=min(l,a[now].len);
            maxx[now]=max(maxx[now],l);
        }
    }
}        
void dfs(int x){
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        dfs(y);
        maxx[x]=max(maxx[x],maxx[y]);
    }
    a[x].minn=min(a[x].minn,min(maxx[x],a[x].len));
}
void dfs1(int x){
    ans=max(ans,a[x].minn);
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        dfs1(y);
    }
}
int main(){
    build();
    scanf("%d",&n);
    scanf("%d",&lens);
    first=lens;
    for(int i=1,x;i<=lens;i++){
        scanf("%d",&x);
        if(i!=1)insert(x-fro);
        fro=x;
    }
    for(int i=1;i<=siz;i++)add(a[i].link,i);
    for(int i=2;i<=n;i++){
        scanf("%d",&lens);
        for(int j=1,x;j<=lens;j++){
            scanf("%d",&x);
            if(j!=1)s[j-1]=x-fro;
            fro=x;
        }
        memset(maxx,0,sizeof(maxx));
        num++;
        change();
        dfs(0);
    }
    if(num==1)printf("%d\n",first);
    else{
        dfs1(0);
        printf("%d\n",ans+1);
    }
    return 0;
}
P2463 [SDOI2008]Sandy的卡片

 

 

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

持续补完

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

P2852 [USACO06DEC]牛奶模式Milk Patterns

posted @ 2019-12-12 09:41  Chloris_Black  阅读(295)  评论(4编辑  收藏  举报