不是新知识的AC自动机|SA|manacher|PAM|SAM|广义SAM|后缀树

字符串重学

好久了,全忘了,所以重新学一遍

顺手在这里粘个板子,以后忘记了可以来看看

毕竟每个人的板子都是不一样的,去网上搜可能会猝死

AC自动机

这个就是先建立一颗\(trie\)树,然后\(build\)的题

code
struct AC{
    int tr[N*T][26],sum[N*T],fail[N*T],seg;
    void init(){
        memset(tr,0,sizeof(tr));
        memset(sum,0,sizeof(sum));
        memset(fail,0,sizeof(fail));
        seg=0;
    }
    void ins(int x){
        int now=0;
        fo(i,1,ls[x]){
            if(!tr[now][s[x][i]-'a'])tr[now][s[x][i]-'a']=++seg;
            now=tr[now][s[x][i]-'a'];
        }
        ed[x]=now;
    }
    queue<int> q;
    void build(){
        fo(i,0,25)if(tr[0][i])q.push(tr[0][i]);
        while(!q.empty()){
            int x=q.front();q.pop();
            fo(i,0,25){
                if(tr[x][i]){
                    fail[tr[x][i]]=tr[fail[x]][i];
                    q.push(tr[x][i]);
                }
                else tr[x][i]=tr[fail[x]][i];
            }
        }
    }
}ac;

SA

就直接基数排序就行了

倍增的思想,复杂度\(\mathcal{O(nlogn)}\)

\(sa[i]\)表示排名为\(i\)的后缀的第一个字母的位置

\(rk[i]\)表示第一个字母位置在\(i\)的后缀的排名

\(tp[i]\)类似\(sa\)按照第二关键字排序

\(tx[i]\)这个是桶

\(hei[i]\)表示排名为\(i\)的后缀和排名为\(i-1\)的后缀的最长公共前缀(网上都叫这个\(height\)

\(hei\)的话,利用\(hei[i]>=hei[rk[sa[i]-1]]-1\),不会证。。。

code
int n,m;
char a[N];
int sa[N],rk[N],tp[N],tx[N];
int hei[N];
int lg[N],st[N][20];
void get_sa(){
    m=30;
    fo(i,1,n)rk[i]=a[i]-'a'+1,tp[i]=i;
    fo(i,1,m)tx[i]=0;
    fo(i,1,n)tx[rk[i]]++;
    fo(i,1,m)tx[i]+=tx[i-1];
    fu(i,n,1)sa[tx[rk[tp[i]]]--]=tp[i];
    for(int w=1,p=0;p<n;m=p,w<<=1){
        p=0;
        fo(i,1,w)tp[++p]=n-w+i;
        fo(i,1,n)if(sa[i]>w)tp[++p]=sa[i]-w;
        fo(i,1,m)tx[i]=0;
        fo(i,1,n)tx[rk[i]]++;
        fo(i,1,m)tx[i]+=tx[i-1];
        fu(i,n,1)sa[tx[rk[tp[i]]]--]=tp[i];
        swap(tp,rk);rk[sa[1]]=p=1;
        fo(i,2,n){
            if(tp[sa[i]]!=tp[sa[i-1]]||tp[sa[i]+w]!=tp[sa[i-1]+w])p++;
            rk[sa[i]]=p;
        }
    }
    fo(i,1,n)rk[sa[i]]=i;
    return ;
}
void get_hei(){
    int k=0;hei[1]=0;
    fo(i,1,n){
        if(rk[i]==1)continue;
        if(k)k--;
        int j=sa[rk[i]-1];
        while(j+k<=n&&i+k<=n&&a[i+k]==a[j+k])k++;
        hei[rk[i]]=k;
    }
    return ;
}

注意赋初值的时候\(rk[i]=a[i]-'a'+1\)千万别忘记加一!!!!!

注意多测清零,因为双关键字排序时,有可能越界导致提前或滞后跳出\(log\)那层循环

还是都清了吧,保险一点,太危险了。。。。。

how to use

这个东西没啥好说的

就一个排名,一个\(hei\)

经常搭配\(hash\)或者二分或者\(RMQ\)

主要就那么一点点性质,求个本质不同子串

求个排名啊啥的,都挺简单的

关于子串问题:后缀的前缀即为子串

那么我们发现,某一子串的所有出现位置在排好序的数组中一定是连续的一段

于是这个连续段我们就可以通过二分找到,起始点就看大小或者题意给出

例题

LuoguP5108

后缀数组,半个板子吧,但是我没有想到

一开始一直往后缀自动机上想,后来发现是后缀数组,其实是看了看题解

排好序之后,只需要找到最左边就行了

但是较短的字符串的最左端点不一定在排序数组中第一次出现的位置

所以我们要在第一次出现后面二分或者倍增,找到符合条件的一段

所谓符合条件,就是满足公共前缀长度大于要求的长度

在这里面找最左的端点就行了

code
#include<bits/stdc++.h>
using namespace std;
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
inline int read(){
    int s=0,t=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*t;
}
const int N=3e5+5;
const int M=1e7+5;
int sigma,n,m;
char ch[N];
int a[N];
int sa[N],rk[N],tx[M],tp[N];
int ans[N];
void get_sa(){
    m=sigma;
    fo(i,1,n)rk[i]=a[i],tp[i]=i;
    fo(i,1,m)tx[i]=0;
    fo(i,1,n)tx[rk[i]]++;
    fo(i,1,m)tx[i]+=tx[i-1];
    fu(i,n,1)sa[tx[rk[tp[i]]]--]=i;
    for(int w=1,p=0;p<n;m=p,w<<=1){
        p=0;fo(i,1,w)tp[++p]=n-w+i;
        fo(i,1,n)if(sa[i]>w)tp[++p]=sa[i]-w;
        fo(i,1,m)tx[i]=0;
        fo(i,1,n)tx[rk[i]]++;
        fo(i,1,m)tx[i]+=tx[i-1];
        fu(i,n,1)sa[tx[rk[tp[i]]]--]=tp[i];
        swap(rk,tp);rk[sa[1]]=p=1;
        fo(i,2,n){
            if(tp[sa[i]]!=tp[sa[i-1]]||tp[sa[i]+w]!=tp[sa[i-1]+w])p++;
            rk[sa[i]]=p;
        }
    }
    fo(i,1,n)rk[sa[i]]=i;
    return ;
}
int hei[N],lg[N],st[N][20],sf[N][20];
void get_hei(){
    int k=0;hei[1]=0;
    fo(i,1,n){
        if(rk[i]==1)continue;
        if(k)k--;
        int j=sa[rk[i]-1];
        while(i+k<=n&&j+k<=n&&a[i+k]==a[j+k])k++;
        hei[rk[i]]=k;
    }
    lg[0]=-1;fo(i,1,n)lg[i]=lg[i>>1]+1;
    fo(i,1,n)st[i][0]=hei[i];
    fo(j,1,19)fo(i,1,n-(1<<j)+1)st[i][j]=min(st[i][j-1],st[i+(1<<j-1)][j-1]);
    fo(i,1,n)sf[i][0]=sa[i];
    fo(j,1,19)fo(i,1,n-(1<<j)+1)sf[i][j]=min(sf[i][j-1],sf[i+(1<<j-1)][j-1]);
}
int hi(int l,int r){
    if(l==r)return n-sa[l]+1;
    if(l>r)swap(l,r);
    l++;int t=lg[r-l+1];
    return min(st[l][t],st[r-(1<<t)+1][t]);
}
int fi(int l,int r){
    int t=lg[r-l+1];
    return min(sf[l][t],sf[r-(1<<t)+1][t]);
}
int get(int x,int lim){
    int r=x;
    fu(i,19,0){
        int rr=r+(1<<i);
        if(hi(x,rr)>=lim)r=rr;
    }
    return fi(x,r);
}
signed main(){
    sigma=read();n=read();
    if(sigma==26){
        scanf("%s",ch+1);
        fo(i,1,n)a[i]=ch[i]-'a';
    }
    else fo(i,1,n)a[i]=read();
    get_sa();get_hei();
    int mx=0;
    fo(i,1,n){
        int now=n-sa[i]+1;
        if(now<=mx)continue;
        fo(j,mx+1,now){
            ans[j]=get(i,j);
        }mx=now;
    }
    fo(i,1,n)printf("%d ",ans[i]);
}

后缀数组是对所有后缀进行排序,并不是子串

所以想要用后缀数组解决子串问题,只能是找公共前缀,后缀的前缀就是子串嘛

POJ1743 Musical Theme

后缀数组应用的好题

二分,\(check\)的时候直接找到最长公共前缀长度大于\(mid\)的,看出现最早和最晚有没有交

code
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
inline int read(){
    int s=0,t=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*t;
}
const int inf=0x3f3f3f3f;
const int N=2e4+5;
int n,a[N],m;
int sa[N],rk[N],tp[N],tx[N];
int hei[N];
void get_sa(){
    m=200;
    fo(i,1,n)rk[i]=a[i],tp[i]=i;
    fo(i,1,m)tx[i]=0;
    fo(i,1,n)tx[rk[i]]++;
    fo(i,1,m)tx[i]+=tx[i-1];
    fu(i,n,1)sa[tx[rk[tp[i]]]--]=tp[i];
    for(int w=1,p=0;p<n;m=p,w<<=1){
        p=0;fo(i,1,w)tp[++p]=n-w+i;
        fo(i,1,n)if(sa[i]>w)tp[++p]=sa[i]-w;
        fo(i,1,m)tx[i]=0;
        fo(i,1,n)tx[rk[i]]++;
        fo(i,1,m)tx[i]+=tx[i-1];
        fu(i,n,1)sa[tx[rk[tp[i]]]--]=tp[i];
        swap(rk,tp);rk[sa[1]]=p=1;
        fo(i,2,n){
            if(tp[sa[i]]!=tp[sa[i-1]]||tp[sa[i]+w]!=tp[sa[i-1]+w])p++;
            rk[sa[i]]=p;
        }
    }
    fo(i,1,n)rk[sa[i]]=i;
    return ;
}
void get_hei(){
    int k=0;hei[1]=0;
    fo(i,1,n){
        if(rk[i]==1)continue;
        if(k)k--;
        int j=sa[rk[i]-1];
        while(i+k<=n&&j+k<=n&&a[i+k]==a[j+k])k++;
        hei[rk[i]]=k;
    }
    return ;
}
bool check(int x){
    fo(i,1,n){
        int j=i,mn=sa[i-1],mx=sa[i-1];
        while(hei[j]>=x){
            mn=min(mn,sa[j]);
            mx=max(mx,sa[j]);
            j++;
        }
        if(mx-mn>x)return true;
        if(j>i)i=j-1;
    }
    return false;
}
signed main(){
    while(true){
        n=read();if(!n)break;
        fo(i,1,n)a[i]=read();
        n--;fo(i,1,n)a[i]=a[i+1]-a[i]+100;//cout<<a[i]<<" ";cout<<endl;
        get_sa();get_hei();
        // fo(i,1,n)cout<<hei[i]<<" ";cout<<endl;
        int l=0,r=n,mid;
        while(l<r){
            mid=l+r+1>>1;
            if(check(mid))l=mid;
            else r=mid-1;
        }
        printf("%d\n",l+1<5?0:l+1);
    }
}

NOI2016 优秀的拆分

找这种分成两半的东西就直接从中间分开

\(AA\)这样的串可以对正串反串都建立\(SA\)数组

然后卡住一个长度,看看这个区间从左到右匹配加上从右到左匹配够不够长,统计一下个数

最后乘起来输出即可

code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
inline int read(){
    int s=0,t=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*t;
}
const int N=3e4+5;
int T,ans;
int f[N],g[N];
struct SA{
    int n,m;
    char a[N];
    int sa[N],rk[N],tp[N],tx[N];
    int hei[N],lg[N],st[N][20];
    void get_sa(){
        m=30;
        fo(i,1,n)rk[i]=a[i]-'a'+1,tp[i]=i;
        fo(i,1,m)tx[i]=0;
        fo(i,1,n)tx[rk[i]]++;
        fo(i,1,m)tx[i]+=tx[i-1];
        fu(i,n,1)sa[tx[rk[tp[i]]]--]=tp[i];
        for(int w=1,p=0;p<n;m=p,w<<=1){
            p=0;fo(i,1,w)tp[++p]=n-w+i;
            fo(i,1,n)if(sa[i]>w)tp[++p]=sa[i]-w;
            
            fo(i,1,m)tx[i]=0;
            fo(i,1,n)tx[rk[i]]++;
            fo(i,1,m)tx[i]+=tx[i-1];
            fu(i,n,1)sa[tx[rk[tp[i]]]--]=tp[i];
            swap(rk,tp);rk[sa[1]]=p=1;
            fo(i,2,n){
                if(tp[sa[i]]!=tp[sa[i-1]]||tp[sa[i]+w]!=tp[sa[i-1]+w])p++;
                rk[sa[i]]=p;
            }
        }
        fo(i,1,n)rk[sa[i]]=i;
        return ;
    }
    void get_hei(){
        int k=0;hei[1]=0;
        fo(i,1,n){
            if(rk[i]==1)continue;
            if(k)k--;
            int j=sa[rk[i]-1];
            while(i+k<=n&&j+k<=n&&a[i+k]==a[j+k])k++;
            hei[rk[i]]=k;
        }
        lg[0]=-1;fo(i,1,n)lg[i]=lg[i>>1]+1;
        fo(i,1,n)st[i][0]=hei[i];
        fo(j,1,16)fo(i,1,n-(1<<j)+1)st[i][j]=min(st[i][j-1],st[i+(1<<j-1)][j-1]);
        return ;
    }
    int hi(int l,int r){
        if(!l||!r)return 0;
        if(l==r)return n-sa[l]+1;
        if(l>r)swap(l,r);
        l++;int t=lg[r-l+1];
        return min(st[l][t],st[r-(1<<t)+1][t]);
    }
    void clear(){
        fo(i,0,n+1)sa[i]=tp[i]=rk[i]=tx[i]=hei[i]=f[i]=g[i]=0;
        return ;
    }
}sa,sb;
signed main(){
    T=read();
    while(T--){
        ans=0;
        scanf("%s",sa.a+1);
        sa.n=sb.n=strlen(sa.a+1);
        memcpy(sb.a,sa.a,sizeof(sa.a));
        sa.get_sa();sa.get_hei();
        reverse(sb.a+1,sb.a+sb.n+1);
        sb.get_sa();sb.get_hei();
        fo(len,1,sa.n>>1){
            for(int i=1;i+len<=sa.n;i+=len){
                int zi1=i,zi2=i+len;
                int fi1=sa.n-i+1,fi2=sa.n-i-len+1;
                int s1=sa.hi(sa.rk[zi1],sa.rk[zi2]),s2=sb.hi(sb.rk[fi2],sb.rk[fi1]);
                // cout<<zi1<<" "<<zi2<<" "<<s1<<" "<<s2<<endl;
                if(s1+s2<=len)continue;
                s1=min(s1,len);s2=min(s2,len);
                f[zi2+len-s2]++;f[min(zi2+s1,sa.n+1)]--;
                g[zi1-s2+1]++;g[zi1-len+s1+1]--;
                // cout<<"cf "<<zi2+len-s2<<" "<<zi2+s1<<" "<<zi1-s2+1<<" "<<zi1-len+s1+1<<endl;
            }
        }
        fo(i,1,sa.n)f[i]+=f[i-1],g[i]+=g[i-1],ans+=f[i-1]*g[i];
        printf("%lld\n",ans);
        sa.clear();sb.clear();
    }
}

LOJ6494 LJJ的字符串

这个一开始看还真的不会,只能拿到20分

然后去颓题解了......

把重复的那一段从两个串的开头分别截去,然后变成了\(AA\)样子的字符串

于是我们可以按照上一个题一样去统计个数

然后各种差分前缀和,难写,但是也再一次验证了,我代码能力还可以??

壮观的代码!!!

code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
inline int read(){
    int s=0,t=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*t;
}
const int mod=1e9+7;
const int N=5e5+5;
int n,m,ans[N],res[N],oth[N],tmp[N],tmp2[N];
char a[N];
struct SA{
    int sa[N],rk[N],tx[N],tp[N];
    int hei[N],lg[N],st[N][20];
    void get_sa(){
        m=30;
        fo(i,1,n)rk[i]=a[i]-'a'+1,tp[i]=i;
        fo(i,1,m)tx[i]=0;
        fo(i,1,n)tx[rk[i]]++;
        fo(i,1,m)tx[i]+=tx[i-1];
        fu(i,n,1)sa[tx[rk[tp[i]]]--]=tp[i];
        for(int w=1,p=0;p<n;m=p,w<<=1){
            p=0;fo(i,1,w)tp[++p]=n-w+i;
            fo(i,1,n)if(sa[i]>w)tp[++p]=sa[i]-w;
            fo(i,1,m)tx[i]=0;
            fo(i,1,n)tx[rk[i]]++;
            fo(i,1,m)tx[i]+=tx[i-1];
            fu(i,n,1)sa[tx[rk[tp[i]]]--]=tp[i];
            swap(tp,rk);rk[sa[1]]=p=1;
            fo(i,2,n){
                if(tp[sa[i]]!=tp[sa[i-1]]||tp[sa[i]+w]!=tp[sa[i-1]+w])p++;
                rk[sa[i]]=p;
            }
        }
        fo(i,1,n)rk[sa[i]]=i;
        return ;
    }
    void get_hei(){
        int k=0;hei[1]=0;
        fo(i,1,n){
            if(rk[i]==1)continue;
            if(k)k--;
            int j=sa[rk[i]-1];
            while(j+k<=n&&i+k<=n&&a[i+k]==a[j+k])k++;
            hei[rk[i]]=k;
        }
        lg[0]=-1;fo(i,1,n)lg[i]=lg[i>>1]+1;
        fo(i,1,n)st[i][0]=hei[i];
        fo(j,1,19)fo(i,1,n-(1<<j)+1)st[i][j]=min(st[i][j-1],st[i+(1<<j-1)][j-1]);
    }
    int hi(int l,int r){
        l=rk[l],r=rk[r];
        if(l==r)return n-sa[l]+1;
        if(l>r)swap(l,r);
        l++;int t=lg[r-l+1];
        return min(st[l][t],st[r-(1<<t)+1][t]);
    }
}p,q;
signed main(){
    scanf("%s",a+1);
    n=strlen(a+1);
    p.get_sa();p.get_hei();
    reverse(a+1,a+n+1);
    q.get_sa();q.get_hei();
    fo(len,1,n>>1){
        for(int i=len;i+len<=n;i+=len){
            int pz1=i,pz2=i+len;
            int qz1=n-pz1+1,qz2=n-pz2+1;
            int lp=p.hi(pz1,pz2),lq=q.hi(qz2,qz1);
            if(lp+lq<=len)continue;
            int beg,end,sum,oh=0;
            if(lq>len){
                beg=pz2;
                if(lq==len+1)sum=len;
                else sum=(lq-1+mod)%mod,oh=(len+1+lq-1)*(lq-1-len)/2%mod;
            }
            else {
                beg=pz2+len-lq+1;
                sum=len%mod;
            }
            if(lp>len)end=pz2+len-1;
            else end=pz2+lp-1;
            oth[beg]=(oth[beg]+oh)%mod;
            oth[end+1]=(oth[end+1]-oh+mod)%mod;
            ans[beg]=(ans[beg]+sum)%mod;
            ans[end+1]=(ans[end+1]-sum+mod)%mod;
            res[beg]=(res[beg]+1)%mod;
            res[end+1]=(res[end+1]-1+mod)%mod;
            tmp[end+1]=(tmp[end+1]-(end+1-beg)%mod+mod)%mod;
            if(end>=beg)tmp2[end+1]=(tmp2[end+1]-((end+1-beg+1)*(end+1-beg)/2+(end+1-beg)*sum)%mod+mod)%mod;
        }
    }
    fo(i,1,n)res[i]=(res[i]+res[i-1])%mod;
    fo(i,1,n)res[i]=(res[i]+tmp[i])%mod;
    fo(i,1,n)res[i]=(res[i]+res[i-1])%mod;
    fo(i,1,n)oth[i]=(oth[i]+oth[i-1])%mod;
    fo(i,1,n)ans[i]=(ans[i]+ans[i-1])%mod;
    fo(i,1,n)ans[i]=(res[i]+ans[i])%mod;
    fo(i,1,n)ans[i]=(ans[i]+tmp2[i])%mod;
    fo(i,1,n)ans[i]=(ans[i]+ans[i-1])%mod;
    fo(i,1,n)ans[i]=(ans[i]+oth[i])%mod;
    fo(i,1,n)ans[i]=(ans[i]+ans[i-1])%mod;
    fo(i,1,n)printf("%lld\n",ans[i]);
}

LOJ6198 谢特

去网上看题解,人家说这是板子题

我人傻了。。。

用后缀数组,求出排名之后的\(hei\)数组

然后做一个单调栈,找到每一个\(hei\)控制的区间

看看左边大还是右边大,枚举小的一侧,在\(trie\)树上查找另一侧

因为区间无交,并且我们是按秩枚举,所以复杂度是启发式合并的\(\mathcal{O(nlnn)}\)

code
#include<bits/stdc++.h>
using namespace std;
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
inline int read(){
    int s=0,t=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*t;
}
const int N=1e5+5;
int n,m,w[N],ans;
char a[N];
int sa[N],rk[N],tx[N],tp[N];
int hei[N],lg[N],st[N][20];
void get_sa(){
    m=30;
    fo(i,1,n)rk[i]=a[i]-'a'+1,tp[i]=i;
    fo(i,1,m)tx[i]=0;
    fo(i,1,n)tx[rk[i]]++;
    fo(i,1,m)tx[i]+=tx[i-1];
    fu(i,n,1)sa[tx[rk[tp[i]]]--]=tp[i];
    for(int w=1,p=0;p<n;m=p,w<<=1){
        p=0;fo(i,1,w)tp[++p]=n-w+i;
        fo(i,1,n)if(sa[i]>w)tp[++p]=sa[i]-w;
        fo(i,1,m)tx[i]=0;
        fo(i,1,n)tx[rk[i]]++;
        fo(i,1,m)tx[i]+=tx[i-1];
        fu(i,n,1)sa[tx[rk[tp[i]]]--]=tp[i];
        swap(rk,tp);rk[sa[1]]=p=1;
        fo(i,2,n){
            if(tp[sa[i]]!=tp[sa[i-1]]||tp[sa[i]+w]!=tp[sa[i-1]+w])p++;
            rk[sa[i]]=p;
        }
    }
    fo(i,1,n)rk[sa[i]]=i;
    return ;
}
void get_hei(){
    int k=0;hei[1]=0;
    fo(i,1,n){
        if(rk[i]==1)continue;
        if(k)k--;
        int j=sa[rk[i]-1];
        while(j+k<=n&&i+k<=n&&a[i+k]==a[j+k])k++;
        hei[rk[i]]=k;
    }
    lg[0]=-1;fo(i,1,n)lg[i]=lg[i>>1]+1;
    fo(i,1,n)st[i][0]=hei[i];
    fo(j,1,19)fo(i,1,n-(1<<j)+1)st[i][j]=min(st[i][j-1],st[i+(1<<j-1)][j-1]);
    return ;
}
int hi(int l,int r){
    l=rk[l];r=rk[r];
    if(l==r)return n-sa[l]+1;
    if(l>r)swap(l,r);
    l++;int t=lg[r-l+1];
    return min(st[l][t],st[r-(1<<t)+1][t]);
}
int tr[N*30][2],sum[N*30],rt[N],seg;
void ins(int prt,int &rt,int x,int d){
    rt=++seg;
    tr[rt][1]=tr[prt][1];tr[rt][0]=tr[prt][0];
    sum[rt]=sum[prt];
    if(d<=-1)return sum[rt]++,void();
    int to=(x>>d)&1;
    ins(tr[prt][to],tr[rt][to],x,d-1);
    if(tr[rt][0]>3000000||tr[rt][0]<0)cout<<"SB"<<endl;
    if(tr[rt][1]>3000000||tr[rt][1]<0)cout<<"SB"<<endl;
    sum[rt]=sum[tr[rt][0]]+sum[tr[rt][1]];
    return ;
}
int query(int l,int r,int x,int d){
    if(d==-1||sum[r]-sum[l]<=0)return 0;
    int to=((x>>d)&1)^1;
    if(sum[tr[r][to]]-sum[tr[l][to]]>0)
        return query(tr[l][to],tr[r][to],x,d-1)|(1<<d);
    else return query(tr[l][to^1],tr[r][to^1],x,d-1);
}
int sta[N],top,l[N],r[N];
signed main(){
    n=read();
    scanf("%s",a+1);
    fo(i,1,n)w[i]=read();
    get_sa();get_hei();
    fo(i,1,n)ins(rt[i-1],rt[i],w[sa[i]],20);
    fo(i,1,n){
        while(top&&hei[sta[top]]>hei[i])r[sta[top]]=i-1,top--;
        l[i]=sta[top]+1;sta[++top]=i;
    }
    while(top)r[sta[top]]=n,top--;
    fo(i,2,n){
        // cout<<i<<" "<<sa[i]<<" "<<l[i]<<" "<<r[i]<<endl;
        if(i-l[i]<=r[i]-i+1)fo(j,l[i]-1,i-1)ans=max(ans,hei[i]+query(rt[i-1],rt[r[i]],w[sa[j]],20));
        else fo(j,i,r[i])ans=max(ans,hei[i]+query(rt[l[i]-2],rt[i-1],w[sa[j]],20));
        // cout<<ans<<endl;
    }
    printf("%d",ans);
}

Luogu7409 SvT

据说有虚树+后缀树的做法,然而我一开始并不知道

于是敲出了一个后缀数组+单调栈的做法

code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
inline int read(){
    int s=0,t=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*t;
}
const int mod=23333333333333333ll;
const int N=5e5+5;
int n,m,q;
int tq;
char a[N];
int sa[N],rk[N],tp[N],tx[N];
int hei[N],lg[N],st[N][20];
void get_sa(){
    m=30;
    fo(i,1,n)rk[i]=a[i]-'a'+1,tp[i]=i;
    fo(i,1,m)tx[i]=0;
    fo(i,1,n)tx[rk[i]]++;
    fo(i,1,m)tx[i]+=tx[i-1];
    fu(i,n,1)sa[tx[rk[tp[i]]]--]=tp[i];
    for(int w=1,p=0;p<n;m=p,w<<=1){
        p=0;fo(i,1,w)tp[++p]=n-w+i;
        fo(i,1,n)if(sa[i]>w)tp[++p]=sa[i]-w;
        fo(i,1,m)tx[i]=0;
        fo(i,1,n)tx[rk[i]]++;
        fo(i,1,m)tx[i]+=tx[i-1];
        fu(i,n,1)sa[tx[rk[tp[i]]]--]=tp[i];
        swap(rk,tp);rk[sa[1]]=p=1;
        fo(i,2,n){
            if(tp[sa[i]]!=tp[sa[i-1]]||tp[sa[i]+w]!=tp[sa[i-1]+w])p++;
            rk[sa[i]]=p;
        }
    }
    fo(i,1,n)rk[sa[i]]=i;
    return ;
}
void get_hei(){
    int k=0;hei[1]=0;
    fo(i,1,n){
        if(rk[i]==1)continue;
        if(k)k--;
        int j=sa[rk[i]-1];
        while(i+k<=n&&j+k<=n&&a[i+k]==a[j+k])k++;
        hei[rk[i]]=k;
    }
    lg[0]=-1;fo(i,1,n)lg[i]=lg[i>>1]+1;
    fo(i,1,n)st[i][0]=hei[i];
    fo(j,1,19)fo(i,1,n-(1<<j)+1)st[i][j]=min(st[i][j-1],st[i+(1<<j-1)][j-1]);
    return ;
}
int hi(int l,int r){
    if(!l||!r)return n-sa[l+r]+1;
    if(l==r)return n-sa[l]+1;
    if(l>r)swap(l,r);
    l++;int t=lg[r-l+1];
    return min(st[l][t],st[r-(1<<t)+1][t]);
}
struct noda{
    int x,s;
    bool operator == (noda a){
        return a.x==x&&a.s==s;
    }
}qus[N*20];
bool com(noda x,noda y){return rk[x.x]<rk[y.x];}
struct node{
    int sum,mn;
}sta[N*20];
int top;
signed main(){
    n=read();q=read();
    scanf("%s",a+1);
    get_sa();get_hei();
    while(q--){
        int s1=read();tq=0;
        fo(i,1,s1)qus[++tq].x=read();
        sort(qus+1,qus+tq+1,com);
        tq=unique(qus+1,qus+tq+1)-qus-1;
        top=0;int now=0,ans=0,las=0;
        fo(i,1,tq){
            int thi=hi(las,rk[qus[i].x]);
            node ts=node{0,thi};
            while(top&&sta[top].mn>=thi){
                now=(now-sta[top].mn*sta[top].sum%mod+mod)%mod;
                ts.sum=(ts.sum+sta[top].sum)%mod;
                top--;
            }
            if(ts.sum)sta[++top]=ts;
            now=(now+ts.sum*ts.mn%mod)%mod;
            ans=(ans+now)%mod;
            sta[++top]=node{1,n-qus[i].x+1};now=(now+n-qus[i].x+1)%mod;
            las=rk[qus[i].x];
        }
        printf("%lld\n",ans);
    }
}

manacher

利用超级回文串的半径搞定

没啥意思,直接上代码

这里的\(p[i]\)表示以\(i\)为回文中心的最长回文半径,包括自己

也就是说,这个数组减一之后,是原串的回文长度

\(mx\)是超级回文串的右端点

\(id\)是超级回文串的中心

\(a[0]\)必须赋上一个其他特殊字符,防止越界!!!!

code
void manacher(){
    a[0]='$';
    fu(i,n,1){
        a[i*2]=a[i];
        a[i*2-1]='#';
    }
    a[n*2+1]='#';
    int id,mx=0;
    fo(i,1,2*n+1){
        if(i<mx)p[i]=min(p[id*2-i],mx-i);
        else p[i]=1;
        while(a[i+p[i]]==a[i-p[i]])p[i]++;
        if(mx<i+p[i])mx=i+p[i],id=i;
        ans=max(ans,p[i]);
    }
}

how to use

目前还没有练到较好的\(manacher\)的练习题

利用回文半径找到某一区间内的所有回文串的个数

可以求解不想交的区间个数。

这是一大类题,所需要求的解和两个区间有关,我们可以分开看

PAM

这个好像比\(manacher\)更加好用啊!!

每次直接插入一个新的字符

回文自动机上,每一个节点都表示一个本质不同的字符串

\(fail\)指针指向的是当前回文串的最长回文后缀(不包括自己)

这样我们每次插入的时候找到前面的串的最长回文后缀并且可以匹配上当前字符的节点

直接接上就好了!!

注意0表示偶数回文串的根,1表示奇数回文串的根

\(len[i]\)表示\(i\)节点表示的回文串的长度,所以\(len[1]=-1\)

挺好理解的,看代码呗。

code
struct PAM{
    struct POT{
        int len,fail,son[26];
    }tr[N];
    int seg,las,ln;
    char a[N];
    PAM(){
        seg=1;tr[0].fail=1;
        tr[1].len=-1;
    }
    int get_fail(int x,int i){
        while(a[i-tr[x].len-1]!=a[i])x=tr[x].fail;
        return x;
    }
    void ins(int x){
        int pos=get_fail(las,x);
        if(!tr[pos].son[a[x]-'a']){
            tr[++seg].fail=tr[get_fail(tr[pos].fail,x)].son[a[x]-'a'];
            tr[pos].son[a[x]-'a']=seg;
            tr[seg].len=tr[pos].len+2;
        }
        las=tr[pos].son[a[x]-'a'];
    }
};

发现少了点东西

经常还会用到一个东西,就是长度小于当前回文串的一半的最长回文子串

这个用来做回文串嵌套的时候非常的顺手

用的时候注意特判,小于2的时候直接连到根就行了

不用管是否会连到自己,因为小于一半的一定不是自己

没有的话也直接连到根,就是没有了

\(fail\)还挺像的

code
struct PAM{
    struct POT{
        int len,fail,half,son[26];
    }tr[N];
    int seg,las,ln;
    char a[N];
    int num[N];
    PAM(){
        seg=1;tr[0].fail=1;
        tr[1].len=-1;
    }
    int get_fail(int x,int i){
        while(a[i-tr[x].len-1]!=a[i])x=tr[x].fail;
        return x;
    }
    int get_half(int x,int l,int i){
        while(tr[x].len+2>l/2||a[i-tr[x].len-1]!=a[i])x=tr[x].fail;
        return x;
    }
    void ins(int x){
        int pos=get_fail(las,x);
        if(!tr[pos].son[a[x]-'a']){
            tr[++seg].fail=tr[get_fail(tr[pos].fail,x)].son[a[x]-'a'];
            tr[pos].son[a[x]-'a']=seg;
            tr[seg].len=tr[pos].len+2;
            if(tr[seg].len<=2)tr[seg].half=tr[seg].fail;
            else tr[seg].half=tr[get_half(tr[pos].half,tr[seg].len,x)].son[a[x]-'a'];
        }
        las=tr[pos].son[a[x]-'a'];
        num[las]++;
        return ;
    }
    void build_num(){
        fu(i,seg,2)num[tr[i].fail]+=num[i];
        num[1]=num[0]=0;
        fo(i,2,seg){
            if(tr[i].len/2==tr[tr[i].half].len)f[i]=f[tr[i].half]+1;
            else f[i]=1;
            an[f[i]]+=num[i];
        }
    }
}pam;

用到这个东西的例题

how to use

对于\(PAM\)来说

保存了一个串的所有本质不同的回文串

这个串只有\(\mathcal{O(n)}\)

所以我们在爆扫自动机的时候复杂度是对的

可以在自动机上\(dfs\)

可以利用不超过长度一半的子回文串来解决回文串嵌套的问题,就是上面那个例题

关于DP

这个东西的动态规划主要是关于回文嵌套或者回文并列的

因为每一个节点都表示一个回文串,我们可以跳来跳去

注意转移的时候要用\(bfs\)保证需要的值在之前都转移过

例题

CF906E

这里是题解

CF932G

这个和上面那个是几乎一样的,都是利用等差数列,挺好的题!!

SAM

这个最难了,再次加深了理解

首先是\(endpos\)这个就是每一个子串所有出现位置的终点集合

这个确实是挺绕口的

这个东西性质非常的多,可以直接借鉴\(OI-wiki\)

就不再写下来了

这里的\(fail\)指针,也就是\(parent\)数上的连向父亲的边

指的是\(endpos\)集合的变大

具体一点就是指向了一个原串的后缀,虽然这个说法极其的不严谨,但是也有那么一点意思

其实是从一个后缀集合指向另一个后缀集合,指的过程中,\(endpos\)集合扩大了

一般是用来\(DP\)的,还有遍历的时候可以用到

注意\(num\)数组不要放在结构体中,不然直接赋值你可以调一年

注意!!!空间开两倍,因为最多有\(2n-1\)个节点!!!

详见代码,后缀自动机模板

code
#include<bits/stdc++.h>
using namespace std;
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
inline int read(){
    int s=0,t=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*t;
}
const int N=1e6+5;
int n,ans,num[N*2];
char a[N];
struct SAM{
    struct POT{
        int len,fail,son[26];
    }tr[N*2];
    int seg,las;
    SAM(){seg=las=1;}
    void ins(int c){
        int p=las,np=++seg;
        las=np;num[np]=1;
        tr[np].len=tr[p].len+1;
        while(p&&!tr[p].son[c])tr[p].son[c]=np,p=tr[p].fail;
        if(!p)tr[np].fail=1;
        else {
            int q=tr[p].son[c];
            if(tr[q].len==tr[p].len+1)tr[np].fail=q;
            else {
                int nq=++seg;
                tr[nq]=tr[q];
                tr[nq].len=tr[p].len+1;
                tr[q].fail=tr[np].fail=nq;
                while(p&&tr[p].son[c]==q)tr[p].son[c]=nq,p=tr[p].fail;
            }
        }
    }
    // void dfs(int x){
    //     if(num[x]!=1)ans=max(ans,num[x]*tr[x].len);
    //     fo(i,0,25)if(tr[x].son[i])dfs(tr[x].son[i]);
    // }
}s;
int buc[N*2],who[N*2];
signed main(){
    scanf("%s",a+1);
    n=strlen(a+1);
    fo(i,1,n)s.ins(a[i]-'a');
    fo(i,1,s.seg)buc[s.tr[i].len]++;
    fo(i,1,s.seg)buc[i]+=buc[i-1];
    fo(i,1,s.seg)who[buc[s.tr[i].len]--]=i;
    fu(i,s.seg,1){
        int u=who[i];
        num[s.tr[u].fail]+=num[u];
        if(num[u]>1)ans=max(ans,num[u]*s.tr[u].len);
    }
    // s.dfs(1);
    printf("%d",ans);
}

how to use

目前对于\(SAM\)的运用还是不太熟练

主要是用在一些本质不同的串上

也可以两个串的后缀自动机一起跑,可以找到相同子串

还有\(endpos\)集合扩大的时候,长度是跳着走的,可以利用差分

一般\(SAM\)是不可以爆扫的,大多利用拓扑排序或者先基数排序然后直接循环

关于DP

在后缀自动机上的\(dp\),我目前做到的都和\(endpos\)集合有关

可以用线段树合并维护每一个点的\(endpos\)集合

然后转移的话,一般来说就是当前子串的后缀可以转移过来

例题

CF700E&LOJ6288

这个就是用线段树合并把\(endpos\)集合维护出来,然后转移就行了

一个结论就是,我可以只选取所有后缀中\(dp\)值最大的转移,因为如果小的话,即使我能加一,也不会比最大的好

注意这个最大的要选取最短的那一个

code
#include<bits/stdc++.h>
using namespace std;
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
inline int read(){
    int s=0,t=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*t;
}
const int N=2e5+5;
int n;
char a[N];
struct SAM{
    struct POT{
        int len,fail,son[26];
    }tr[N*2];
    int seg,las;
    SAM(){seg=las=1;}
    int rt[N*2],f[N*2],tp[N*2];
    struct XDS{
        int ls[N*80],rs[N*80];
        int sum[N*80],tot;
        void pushup(int x){
            sum[x]=sum[ls[x]]+sum[rs[x]];
            return ;
        }
        void ins(int &x,int l,int r,int pos){
            if(!x)x=++tot;
            if(l==r)return sum[x]=1,void();
            int mid=l+r>>1;
            if(pos<=mid)ins(ls[x],l,mid,pos);
            else ins(rs[x],mid+1,r,pos);
            pushup(x);return ;
        }
        int getone(int x,int l,int r){
            if(l==r)return l;
            int mid=l+r>>1;
            if(sum[ls[x]])return getone(ls[x],l,mid);
            return getone(rs[x],mid+1,r);
        }
        int query(int x,int l,int r,int ql,int qr){
            if(!x)return 0;
            if(ql<=l&&r<=qr)return sum[x];
            int mid=l+r>>1,ret=0;
            if(ql<=mid)ret+=query(ls[x],l,mid,ql,qr);
            if(qr>mid)ret+=query(rs[x],mid+1,r,ql,qr);
            return ret;
        }
        int merge(int x,int y){
            if(!x||!y)return x+y;
            int z=++tot;
            ls[z]=merge(ls[x],ls[y]);
            rs[z]=merge(rs[x],rs[y]);
            pushup(z);return z;
        }
    }xds;
    void ins(int c,int pos){
        int p=las,np=++seg;
        las=np;
        tr[np].len=tr[p].len+1;
        while(p&&!tr[p].son[c])tr[p].son[c]=np,p=tr[p].fail;
        if(!p)tr[np].fail=1;
        else {
            int q=tr[p].son[c];
            if(tr[q].len==tr[p].len+1)tr[np].fail=q;
            else {
                int nq=++seg;
                tr[nq]=tr[q];
                tr[nq].len=tr[p].len+1;
                tr[q].fail=tr[np].fail=nq;
                while(p&&tr[p].son[c]==q)tr[p].son[c]=nq,p=tr[p].fail;
            }
        }
        xds.ins(rt[las],1,n,pos);
    }
    int to[N*2],nxt[N*2],head[N*2],rp;
    void add_edg(int x,int y){
        to[++rp]=y;
        nxt[rp]=head[x];
        head[x]=rp;
    }
    void dfs(int x){
        for(int i=head[x];i;i=nxt[i]){
            dfs(to[i]);
            rt[x]=xds.merge(rt[x],rt[to[i]]);
        }
    }
    void build(){
        fo(i,1,n)ins(a[i]-'a',i);
        fo(i,2,seg)add_edg(tr[i].fail,i);
        dfs(1);
    }
    int buc[N*2],who[N*2];
    void work(){
        int ans=0;build();
        fo(i,1,seg)buc[tr[i].len]++;
        fo(i,1,n)buc[i]+=buc[i-1];
        fo(i,1,seg)who[buc[tr[i].len]--]=i;
        fo(i,2,seg){
            int u=who[i];
            int fa=tp[tr[u].fail];
            f[u]=1;int ps=xds.getone(rt[u],1,n);
            if(xds.query(rt[fa],1,n,ps-tr[u].len+tr[fa].len,ps)>1){
                f[u]=f[fa]+1;
                tp[u]=u;
            }
            else if(f[u]<=f[fa]){
                f[u]=f[fa];tp[u]=fa;
            }
            else tp[u]=u;
            ans=max(ans,f[u]);
            // if(f[u]==24)cout<<n<<" "<<u<<endl;
        }
        printf("%d",ans);
    }
}sam;
signed main(){
    n=read();
    scanf("%s",a+1);
    sam.work();
}

LOJ6401

这个也是一种套路

在后缀自动机上遍历,同时还需要满足序列上的某一种条件

这样的话我们就要再加一个变量,存储在序列满足条件的最大或者最小长度,这个题是最大长度

然后在后缀自动机上加加减减就好了

code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
inline int read(){
    int s=0,t=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*t;
}
const int N=1e5+5;
int n,k,pr[N];
char a[N],b[N];
struct SAM{
    struct POT{
        int len,mxa,fail,son[26];
    }tr[N*2];
    int seg,las;
    SAM(){seg=las=1;}
    void ins(int c,int l){
        int p=las,np=++seg;
        las=np;tr[np].len=tr[p].len+1;
        while(p&&!tr[p].son[c])tr[p].son[c]=np,p=tr[p].fail;
        if(!p)tr[np].fail=1;
        else {
            int q=tr[p].son[c];
            if(tr[q].len==tr[p].len+1)tr[np].fail=q;
            else {
                int nq=++seg;
                tr[nq]=tr[q];
                tr[nq].len=tr[p].len+1;
                tr[np].fail=tr[q].fail=nq;
                while(p&&tr[p].son[c]==q)tr[p].son[c]=nq,p=tr[p].fail;
            }
        }
        tr[las].mxa=max(tr[las].mxa,l);
        return ;
    }
    int buc[N*2],who[N*2];
    void work(){
        int ans=0;
        fo(i,1,n){
            int l=0,r=i,mid;
            while(l<r){
                mid=l+r+1>>1;
                if(pr[i]-pr[i-mid]<=k)l=mid;
                else r=mid-1;
            }
            ins(a[i]-'a',l);
        }
        fo(i,1,seg)buc[tr[i].len]++;
        fo(i,1,n)buc[i]+=buc[i-1];
        fo(i,1,seg)who[buc[tr[i].len]--]=i;
        fu(i,seg,2){
            int u=who[i];
            tr[tr[u].fail].mxa=max(tr[tr[u].fail].mxa,tr[u].mxa);
        }
        fo(i,2,seg){
            int u=who[i];
            ans+=max(0ll,min(tr[u].mxa,tr[u].len)-tr[tr[u].fail].len);
        }
        printf("%lld",ans);
    }
}sam;
signed main(){
    scanf("%s",a+1);
    n=strlen(a+1);
    scanf("%s",b+1);
    fo(i,1,n){
        pr[i]=b[i]-'0';
        pr[i]^=1;
        pr[i]+=pr[i-1];
    }k=read();
    sam.work();
}

如果再要求一个范围的话,就变成NOI2018你的名字毒瘤题

SP7258 SUBLEX

这其实就是一个超级大板子题,求第\(k\)小的本质不同的串

于是乎,我在输出的\(dfs\)中向下递归完之后,并没有\(return\)

导致我调了一个小时......

code
#include<bits/stdc++.h>
using namespace std;
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
inline int read(){
    int s=0,t=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*t;
}
const int N=9e4+5;
int n,T;
char a[N];
struct SAM{
    struct POT{
        int len,fail,son[26];
    }tr[N*2];
    int seg,las;
    int num[N*2],sum[N*2];
    SAM(){seg=las=1;}
    void ins(int c){
        int p=las,np=++seg;
        las=np;tr[np].len=tr[p].len+1;
        while(p&&!tr[p].son[c])tr[p].son[c]=np,p=tr[p].fail;
        if(!p)tr[np].fail=1;
        else {
            int q=tr[p].son[c];
            if(tr[q].len==tr[p].len+1)tr[np].fail=q;
            else {
                int nq=++seg;
                tr[nq]=tr[q];
                tr[nq].len=tr[p].len+1;
                tr[np].fail=tr[q].fail=nq;
                while(p&&tr[p].son[c]==q)tr[p].son[c]=nq,p=tr[p].fail;
            }
        }
        return ;
    }
    void dfs(int x,int rk){
        rk-=num[x];
        if(rk<=0)return ;
        fo(i,0,25){
            if(!tr[x].son[i])continue;
            if(sum[tr[x].son[i]]>=rk){
                printf("%c",i+'a');
                dfs(tr[x].son[i],rk);
                return ;//递归不返回,爆零两行泪
            }
            rk-=sum[tr[x].son[i]];
        }
    }
    int buc[N*2],who[N*2];
    void work(){
        fo(i,1,n)ins(a[i]-'a');
        fo(i,1,seg)buc[tr[i].len]++;
        fo(i,1,n)buc[i]+=buc[i-1];
        fo(i,1,seg)who[buc[tr[i].len]--]=i;
        fo(i,2,seg)num[who[i]]=1;
        fu(i,seg,2){
            int u=who[i];
            sum[u]=num[u];
            fo(j,0,25)sum[u]+=sum[tr[u].son[j]];
        }
        while(T--){
            int rk=read();
            dfs(1,rk);
            printf("\n");
        }
    }
}sam;
signed main(){
    scanf("%s",a+1);
    n=strlen(a+1);
    T=read();
    sam.work();
}

广义SAM

这个据说网上好多都是错误的,所以在自己的博客中写一点理解,方便查看

定义就是将一堆字符串插入到后缀自动机上

你也可以按照一般的套路,用特殊字符将字符串链接起来,不过效率低下

那就直接上在线构造后缀自动机的板子,从辰星凌那里学的

code
struct SAM{
    struct POT{
        int len,fail,son[26];
    }tr[M*2];
    int seg;
    SAM(){seg=1;}
    int ins(int c,int las){
        if(tr[las].son[c]){
            if(tr[tr[las].son[c]].len==tr[las].len+1)return tr[las].son[c];
            int p=las,nq=++seg,q=tr[las].son[c];
            tr[nq]=tr[q];
            tr[nq].len=tr[p].len+1;
            tr[q].fail=nq;
            while(p&&tr[p].son[c]==q)tr[p].son[c]=nq,p=tr[p].fail;
            return nq;
        }
        int p=las,np=++seg;
        tr[np].len=tr[p].len+1;
        while(p&&!tr[p].son[c])tr[p].son[c]=np,p=tr[p].fail;
        if(!p)tr[np].fail=1;
        else {
            int q=tr[p].son[c];
            if(tr[q].len==tr[p].len+1)tr[np].fail=q;
            else {
                int nq=++seg;
                tr[nq]=tr[q];
                tr[nq].len=tr[p].len+1;
                tr[q].fail=tr[np].fail=nq;
                while(p&&tr[p].son[c]==q)tr[p].son[c]=nq,p=tr[p].fail;
            }
        }
        return np;
    }
}sam;

首先注意\(seg\)的初值是0!!!

还有\(ins\)函数的返回值是下一次插入的\(las\)

这里主要是判断了之前的串中,是否插入过当前这样的字符

如果有并且长度恰好,那就完美了,直接返回这个节点就好了

有一点不确定,如果询问子串个数(可以重复的那种,不用本质不同),这样的返回,应该给返回的这个节点的\(num\)加一

如果没有那么完美,就像普通\(SAM\)一样,新建一个节点,分走一点信息

但是我们不能像原来一样新建两个节点了,因为第一次新建的节点已经存在了,再建一个属于空节点

意会......

那么剩下的就和原来普通的一样了

记住,这个是正版的,好用,不会被卡,其实离线构造没什么用了......

后缀树

说白了就是把后缀自动机的\(fail\)连个反向边,变成了一颗树

好像是可以维护两个子串的最长公共前(后)缀,直接跳\(lca\)就行吧

LCT维护后缀树

Luogu5212 SubString

这个要维护后缀树的子树和,然而不能用线段树,因为动态加点

所以想到\(LCT\),于是我们链加单点查,记得后缀自动机克隆节点的时候别忘了把\(siz\)也赋值过去

code
#include<bits/stdc++.h>
using namespace std;
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
int read(){
    int s=0,t=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')t=-1;ch=getchar();}
    while(isdigit(ch)){s=s*10+ch-'0';ch=getchar();}
    return s*t;
}
const int N=6e5+5;
const int M=3e6+5;
struct LCT{
    struct POT{int siz,tag,fa,son[2];}tr[N*2];
    int pth[N*2],pt;
    void pusht(int x,int v){tr[x].siz+=v;tr[x].tag+=v;}
    void pushdown(int x){
        if(!tr[x].tag)return ;
        if(tr[x].son[0])pusht(tr[x].son[0],tr[x].tag);
        if(tr[x].son[1])pusht(tr[x].son[1],tr[x].tag);
        tr[x].tag=0;
    }
    bool nroot(int x){return tr[tr[x].fa].son[0]==x||tr[tr[x].fa].son[1]==x;}
    int get(int x){return tr[tr[x].fa].son[1]==x;}
    void rotate(int x){
        int y=tr[x].fa,z=tr[y].fa;
        int xpos=get(x),ypos=get(y);
        if(nroot(y))tr[z].son[ypos]=x;
        tr[x].fa=z;tr[y].fa=x;
        tr[y].son[xpos]=tr[x].son[xpos^1];
        tr[tr[x].son[xpos^1]].fa=y;
        tr[x].son[xpos^1]=y;
    }
    void splay(int x){
        int now=x;pth[++pt]=now;
        while(nroot(now))pth[++pt]=now=tr[now].fa;
        while(pt)pushdown(pth[pt--]);
        while(nroot(x)){
            int y=tr[x].fa,z=tr[y].fa;
            int xpos=get(x),ypos=get(y);
            if(nroot(y)){
                if(xpos==ypos)rotate(y);
                else rotate(x);
            }
            rotate(x);
        }
    }
    void access(int x){for(int y=0;x;y=x,x=tr[x].fa)splay(x),tr[x].son[1]=y;}
    void link(int x,int y){splay(x);splay(y);tr[x].fa=y;}
    void cut(int x,int y){splay(x);splay(y);tr[x].fa=0;if(tr[y].son[1]==x)tr[y].son[1]=0;}
    int ask(int x){access(x);splay(x);return tr[x].siz;}
}lct;
struct SAM{
    struct POT{int son[2],fail,len;}tr[N*2];
    int las,seg;
    SAM(){las=seg=1;}
    void ins(int c){
        int p=las,np=++seg;las=np;
        tr[np].len=tr[p].len+1;
        while(p&&!tr[p].son[c])tr[p].son[c]=np,p=tr[p].fail;
        if(!p){tr[np].fail=1;lct.link(np,1);}
        else {
            int q=tr[p].son[c];
            if(tr[q].len==tr[p].len+1){tr[np].fail=q;lct.link(np,q);}
            else {
                int nq=++seg;lct.cut(q,tr[q].fail);//lct.tr[tr[q].fail].siz-=lct.tr[q].siz;
                tr[nq]=tr[q];lct.link(nq,tr[q].fail);lct.tr[nq].siz=lct.tr[q].siz;
                tr[nq].len=tr[p].len+1;
                tr[q].fail=tr[np].fail=nq;
                lct.link(q,nq);lct.link(np,nq);
                while(p&&tr[p].son[c]==q)tr[p].son[c]=nq,p=tr[p].fail;
            }
        }
        lct.access(las);lct.splay(las);lct.pusht(las,1);
    }
}sam;
int Q,mas,n;
char a[M];
void decode(int mask){
    fo(i,0,n-1){
        mask=(mask*131+i)%n;
        swap(a[i+1],a[mask+1]);
    }
}
signed main(){
    Q=read();
    scanf("%s",a+1);n=strlen(a+1);
    fo(i,1,n)sam.ins(a[i]-'A');
    while(Q--){
        char tp[10];scanf("%s",tp+1);
        scanf("%s",a+1);n=strlen(a+1);decode(mas);
        if(tp[1]=='A')fo(i,1,n)sam.ins(a[i]-'A');
        else {
            int now=1,flag=true;
            fo(i,1,n){
                if(!sam.tr[now].son[a[i]-'A']){flag=false;break;}
                now=sam.tr[now].son[a[i]-'A'];
            }
            if(!flag)printf("0\n");
            else {
                int ans=lct.ask(now);
                mas^=ans;printf("%d\n",ans);
            }
        }
    }
}
posted @ 2021-12-08 20:00  fengwu2005  阅读(43)  评论(0编辑  收藏  举报