后缀数组总结

后缀数组总结

后缀数组是解决字符串问题的有力工具,本总结以题目套路为主

1.P2408 不同子串个数

题意:

求字符串本质不同的子串个数

思路:

在后缀数组中子串表现形式为后缀的前缀,求出height数组后,每个字符串的贡献即为 n + 1 − s a [ i ] − h e i g h t [ i ] n+1-sa[i]-height[i] n+1sa[i]height[i],

减去和上一个重复的,保证每个子串唯一即可

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int maxn=1e5+5;
struct SA{ 
    char s[maxn];
    int n,m,a[maxn],sa[maxn],rank[maxn],Height[maxn],tax[maxn],tp[maxn];
    void init(){ 
        scanf("%d",&n);
        scanf("%s",s+1);
        m=(int)'z';
    }
    int cmp(int *f,int x,int y,int w){ 
        return f[x]==f[y]&&f[x+w]==f[y+w];
    }
    void rsort(){ 
        for(int i=0;i<=m;++i)tax[i]=0;
        for(int i=1;i<=n;++i)tax[rank[tp[i]]]++;
        for(int i=1;i<=m;++i)tax[i]+=tax[i-1];
        for(int i=n;i;--i)sa[tax[rank[tp[i]]]--]=tp[i];
    }
    void Suffix(){ 
        for(int i=1;i<=n;++i)a[i]=s[i];
        for(int i=1;i<=n;++i)rank[i]=a[i],tp[i]=i;
        rsort();
        for(int w=1,p=1,i;p<n;w+=w,m=p){ 
            for(p=0,i=n-w+1;i<=n;++i)tp[++p]=i;
            for(i=1;i<=n;++i)if(sa[i]>w)tp[++p]=sa[i]-w;
            rsort(),swap(rank,tp),rank[sa[1]]=p=1;
            for(int i=2;i<=n;++i)
                rank[sa[i]]=cmp(tp,sa[i],sa[i-1],w)?p:++p;
        }
        int j,k=0;
        for(int i=1;i<=n;Height[rank[i++]]=k)
            for(k=k?k-1:k,j=sa[rank[i]-1];a[i+k]==a[j+k];++k);
    }
    void solve(){ 
        ll ans=0;
        for(int i=1;i<=n;++i){ 
            ans+=n+1-sa[i]-Height[i];
        }
        cout<<ans;
    }
}sa;
int main(){ 
    sa.init();
    sa.Suffix();
    sa.solve();
}

2.P2743 乐曲主题Musical Themes

题意:

不可重叠最长重复子串

思路:

二分+height分组,每组间内的lcp都满足指定长度,只要保证每组 m x ( s a [ i ] ) − m n ( s a [ i ] ) mx(sa[i])-mn(sa[i]) mx(sa[i])mn(sa[i])的最大值能够满足>=二分答案即可,注意这题需要差分,有偏移注意细节

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e4+5;
struct SA{//sa字符转后不能有负数(计数排序)
    int n,m,a[maxn],sa[maxn],rank[maxn],Height[maxn],tax[maxn],tp[maxn];
    void init(){
        scanf("%d",&n);
        for(int i=1;i<=n;++i)scanf("%d",&a[i]);
        for(int i=1;i<=n;++i){ 
            a[i]=a[i+1]-a[i];
            a[i]+=88;
        }
        n--;
        m=180;
    }
    int cmp(int*f,int x,int y,int w){ 
        return f[x]==f[y]&&f[x+w]==f[y+w];
    }
    void rsort(){ 
        for(int i=0;i<=m;++i)tax[i]=0;
        for(int i=1;i<=n;++i)tax[rank[tp[i]]]++;
        for(int i=1;i<=m;++i)tax[i]+=tax[i-1];
        for(int i=n;i;--i)sa[tax[rank[tp[i]]]--]=tp[i];
    }
    void Suffix(){ 
        for(int i=1;i<=n;++i)rank[i]=a[i],tp[i]=i;
        rsort();
        for(int w=1,p=1,i;p<n;w+=w,m=p){ 
            for(p=0,i=n-w+1;i<=n;++i)tp[++p]=i;
            for(i=1;i<=n;++i)if(sa[i]>w)tp[++p]=sa[i]-w;
            rsort(),swap(rank,tp),rank[sa[1]]=p=1;
            for(int i=2;i<=n;++i)
                rank[sa[i]]=cmp(tp,sa[i],sa[i-1],w)?p:++p;
        }
        int j,k=0;
        for(int i=1;i<=n;Height[rank[i++]]=k)
            for(k=k?k-1:k,j=sa[rank[i]-1];a[i+k]==a[j+k];++k);
        Height[n+1]=-1;
    }
    bool check(int x){ 
        int mn=sa[1],mx=sa[1];
        for(int i=2;i<=n+1;++i){ 
            if(Height[i]<x){
                if(mx-mn-1>=x){ //差分后相差1
                    return 1;
                } 
                mn=mx=sa[i];
            }else{ 
                mn=min(mn,sa[i]);
                mx=max(mx,sa[i]);
            }
        }
        return 0;
    }
    void solve(){ 
        int l=-1,r=n;
        while(l<r){ 
            int mid=(l+r+1)>>1;
            if(check(mid))
                l=mid;
            else r=mid-1;
        }
        l++;
        cout<<(l<5?0:l);
    }
}sa;
int main(){ 
    sa.init();
    sa.Suffix();
    /*for(int i=1;i<=sa.n;++i){ 
        cout<<sa.sa[i]<<" "<<sa.Height[i]<<"\n";
    }*/
    sa.solve();
    return 0;
}

3.Milk Patterns G

题意:

最长可重叠出现 k k k次子串

思路:

根据 L C P ( i , j ) = m i n ( L C P ( i , k ) , L C P ( k , j ) ) LCP(i,j)=min(LCP(i,k),LCP(k,j)) LCP(i,j)=min(LCP(i,k),LCP(k,j)) i + 1 ≤ k ≤ j i+1\leq k\leq j i+1kj

可知这 k k k个子串一定是连续的,这一定不会更劣。

所以用单调队列维护 k − 1 k-1 k1 h e i g h t height height的最小值即可

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e4+5;
const int M=1e6+5;
struct SA{ 
    int n,m,a[maxn],sa[maxn],rank[maxn],Height[maxn],tax[M],tp[maxn],k,mx=0,q[maxn],h=1,t=0;
    void init(){ 
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;++i)scanf("%d",&a[i]),mx=max(a[i],mx);
        m=mx;
    }
    int cmp(int*f,int x,int y,int w){ 
        return f[x]==f[y]&&f[x+w]==f[y+w];
    }
    void rsort(){ 
        for(int i=0;i<=m;++i)tax[i]=0;
        for(int i=1;i<=n;++i)tax[rank[tp[i]]]++;
        for(int i=1;i<=m;++i)tax[i]+=tax[i-1];
        for(int i=n;i;--i)sa[tax[rank[tp[i]]]--]=tp[i];
    }
    void Suffix(){ 
        for(int i=1;i<=n;++i)rank[i]=a[i],tp[i]=i;
        rsort();
        for(int w=1,p=1,i;p<n;w+=w,m=p){ 
            for(p=0,i=n-w+1;i<=n;++i)tp[++p]=i;
            for(i=1;i<=n;++i)if(sa[i]>w)tp[++p]=sa[i]-w;
            rsort(),swap(rank,tp),rank[sa[1]]=p=1;
            for(int i=2;i<=n;++i)
                rank[sa[i]]=cmp(tp,sa[i],sa[i-1],w)?p:++p;
        }
        int j,k=0;
        for(int i=1;i<=n;Height[rank[i++]]=k)
            for(k=k?k-1:k,j=sa[rank[i]-1];a[i+k]==a[j+k];++k);
    }
    int solve(){ 
        h=1,t=0;
        int mx=0;
        for(int i=1;i<=n;++i){ 
            while(h<=t&&q[h]<i-k+2)h++;
            while(h<=t&&Height[q[t]]>=Height[i])t--;
            q[++t]=i;
            if(i>=k-1){ 
                mx=max(mx,Height[q[h]]);
            }
        }
        return mx;
    }
}sa;
int main(){
    sa.init();
    sa.Suffix();
    /*for(int i=1;i<=sa.n;++i){ 
        cout<<sa.Height[i]<<"\n";
    }*/
    cout<<sa.solve(); 
    return 0;
}

4.bzoj2942 公共串

题意:

n n n个串的最长公共子串

思路:

多串问题处理时,用从未出现的符号拼接多串,并记录每个位置来源的串。

两种方法,二分+ h e i g h t height height分组判断每组是否有所有的串, O ( n l o g n ) O(nlogn) O(nlogn)

求所有类型都包含的区间,事实上是双指针的经典应用,单调队列同时维护区间最小的height值,最后再求个max即可,使用 S A I S SAIS SAIS复杂度为 O ( n ) O(n) O(n)

下面两种方法都给出

二分

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+20;
const int M=130;
char s[maxn];
struct SA{ 
    int n,m,a[maxn],sa[maxn],rank[maxn],Height[maxn],tax[maxn],tp[maxn],k,mx=0,num,pos[maxn];
    void init(){ 
        scanf("%d",&num);
        for(int i=1;i<=num;++i){ 
            scanf("%s",s+1);
            int len=strlen(s+1);
            mx=max(mx,len);
            for(int j=1;j<=len;++j){ 
                a[++n]=s[j],pos[n]=i;
            }
            a[++n]='#'+i;
        }
        n--;
        m=(int)'z';
        //for(int i=1;i<=n;++i)putchar((char)a[i]);
    }
    int cmp(int*f,int x,int y,int w){ 
        return f[x]==f[y]&&f[x+w]==f[y+w];
    }
    void rsort(){ 
        for(int i=0;i<=m;++i)tax[i]=0;
        for(int i=1;i<=n;++i)tax[rank[tp[i]]]++;
        for(int i=1;i<=m;++i)tax[i]+=tax[i-1];
        for(int i=n;i;--i)sa[tax[rank[tp[i]]]--]=tp[i];
    }
    void Suffix(){ 
        for(int i=1;i<=n;++i)rank[i]=a[i],tp[i]=i;
        rsort();
        for(int w=1,p=1,i;p<n;w+=w,m=p){ 
            for(p=0,i=n-w+1;i<=n;++i)tp[++p]=i;
            for(i=1;i<=n;++i)if(sa[i]>w)tp[++p]=sa[i]-w;
            rsort(),swap(rank,tp),rank[sa[1]]=p=1;
            for(int i=2;i<=n;++i)
                rank[sa[i]]=cmp(tp,sa[i],sa[i-1],w)?p:++p;
        }
        int j,k=0;
        for(int i=1;i<=n;Height[rank[i++]]=k)
            for(k=k?k-1:k,j=sa[rank[i]-1];a[i+k]==a[j+k];++k);

        for(int i=1;i<=n;++i)cout<<Height[i]<<" "<<pos[sa[i]]<<"\n";
    }
    bool check(int x){ 
        int st=0;
        st|=(1<<(pos[sa[1]]-1));
        if(st==((1<<num)-1))return 1;
        for(int i=2;i<=n;++i){ 
            if(Height[i]<x){ 
                st=0;
                st|=(1<<(pos[sa[i]]-1));
            }else{ 
                st|=(1<<(pos[sa[i]]-1));
                if(st==((1<<num)-1))return 1;
            }
        }
        return 0;
    }
    void solve(){ 
        int l=0,r=mx;
        while(l<r){ 
            int mid=(l+r+1)>>1;
            if(check(mid))
                l=mid;
            else r=mid-1;
        }
        cout<<l;
    }
}sa;
int main(){ 
    sa.init();
    sa.Suffix();
    //sa.solve();
    return 0;
}

单调队列

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+10;
int q[maxn],h1=1,t1=0;
struct SA{ 
    int s[(maxn<<1)],sa[maxn],rk[maxn],c[maxn],p[maxn],tmp[maxn],n,m,ht[maxn],pos[maxn],num,sum[10],st=0;
    char str[maxn];
    bool t[maxn<<1];
#define Ar(x,a) sa[p[s[x]]a]=x
    void IS(int*s,int*s1,int n1,int n,int m,bool *t){ 
        memset(sa+1,0,n<<2);memset(c+1,0,m<<2);
        for(int i=1;i<=n;++c[s[i++]]);
        for(int i=2;i<=m;++i)c[i]+=c[i-1];
        memcpy(p+1,c+1,m<<2);
        for(int i=n1;i;Ar(s1[i],--),i--);
        for(int i=1;i<=m;++i)p[i]=c[i-1]+1;
        for(int i=1;i<=n;sa[i]>1&&t[sa[i]-1]?Ar(sa[i]-1,++):0,i++);
        memcpy(p+1,c+1,m<<2);
        for(int i=n;i;sa[i]>1&&!t[sa[i]-1]?Ar(sa[i]-1,--):0,i--);
    }
    void SAIS(int s[],bool t[],int tmp[],int n,int m){ 
        int n1=0,m1=rk[1]=0,*s1=s+n;t[n]=0;
        for(int i=n-1;i;t[i]=s[i]^s[i+1]?s[i]>s[i+1]:t[i+1],i--);
        for(int i=2;i<=n;rk[i]=t[i-1]&&!t[i]?tmp[++n1]=i,n1:0,i++);
        IS(s,tmp,n1,n,m,t);
        for(int i=1,x,y=0;i<=n;++i)
            if(x=rk[sa[i]]){ 
                if(m1<=1||tmp[x+1]-tmp[x]!=tmp[y+1]-tmp[y])++m1;
                else for(int a=tmp[x],b=tmp[y];a<=tmp[x+1];a++,b++)
                    if((s[a]<<1|t[a])^(s[b]<<1|t[b])){ ++m1;break;}
                s1[y=x]=m1;
            }
        if(m1<n1)SAIS(s1,t+n,tmp+n1,n1,m1);
        else for(int i=1;i<=n1;sa[s1[i]]=i,i++);
        for(int i=1;i<=n1;s1[i]=tmp[sa[i]],i++);
        IS(s,s1,n1,n,m,t);
    }
    void MakeSA(){ 
        --n;
        for(int i=1;i<=n;++i)sa[i]=sa[i+1],rk[sa[i]]=i;
    }
    void getHt(){ 
        for(int i=1,k=0;i<=n;ht[rk[i]]=k,i++,k&&--k)
            for(int j=sa[rk[i]-1];s[i+k]==s[j+k];k++);
    }
    void init(){ 
        n=0;
        scanf("%d",&num);
        for(int i=1;i<=num;++i){ 
            scanf("%s",str+1);
            int len=strlen(str+1);
            for(int j=1;j<=len;++j){ 
                s[++n]=str[j];pos[n]=i;
            }
            s[++n]='#'+i;
        }
        if(num==1){ 
            cout<<strlen(str+1)<<"\n";exit(0);
        }
        s[n]=1;
        SAIS(s,t,tmp,n,'z'+1);
        MakeSA();
        getHt();
       // for(int i=1;i<=n;++i)cout<<ht[i]<<" "<<pos[sa[i]]<<"\n";
    }
    void add(int x){ 
        if(!pos[sa[x]])return;
        if(++sum[pos[sa[x]]]==1)
            st|=(1<<(pos[sa[x]]-1));
    }
    void del(int x){ 
        if(!pos[sa[x]])return;
        if(--sum[pos[sa[x]]]==0)
            st^=(1<<(pos[sa[x]]-1));
    }
    int solve(){ 
        st=0;
        int mx=ht[1],all=(1<<num)-1;
        add(1);
        q[++t1]=1;
        int r=1,l=1;
        while(r<=n){ 
            while(r<=n&&st!=all){ 
                add(++r);
                while(h1<=t1&&ht[q[t1]]>=ht[r])t1--;
                q[++t1]=r;
            }
            while(l<=r&&st==all)
                del(l++);
            while(h1<=t1&&q[h1]<l)h1++;
            if(h1<=t1)
                mx=max(mx,ht[q[h1]]);
        }
        return mx;
    }
}sa;
int main(){ 
    sa.init();
    cout<<sa.solve();
    return 0;
}


5.bzoj3228 差异

题意:

求[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xDOa6zfF-1628850668766)(C:\Users\98753\AppData\Roaming\Typora\typora-user-images\image-20210801233620226.png)]

思路:

前面两个很显然 每个后缀出现 n − 1 n-1 n1次,就是 n ( n + 1 ) ( n − 1 ) 2 \frac {n(n+1)(n-1)}{2} 2n(n+1)(n1)

当考虑到任意两两后缀的问题时,我们考虑考虑 h e i g h t height height数组的贡献,在一个区间中,只有最小的 h e i g h t height height有贡献,可以用单调队列求出作为最小值的区间,注意为了不重复, L [ i ] L[i] L[i]表示左边第一个<他的数的位置, R [ i ] R[i] R[i]表示右边第一个小于等于他的数的位置,贡献就是 m i n ( h e i g h t ) ∗ ( i − L [ i ] ) ∗ ( i − R [ i ] ) min(height)*(i-L[i])*(i-R[i]) min(height)(iL[i])(iR[i]),注意这种题一定要注意边界!

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+5;
int q[maxn],h,t,L[maxn],R[maxn];
struct SA{ 
    char s[maxn];
    int n,m,a[maxn],sa[maxn],rk[maxn],ht[maxn],tax[maxn],tp[maxn];
    int cmp(int*f,int x,int y,int w){ 
        return f[x]==f[y]&&f[x+w]==f[y+w];
    }
    void rsort(){ 
        for(int i=0;i<=m;++i)tax[i]=0;
        for(int i=1;i<=n;++i)tax[rk[tp[i]]]++;
        for(int i=1;i<=m;++i)tax[i]+=tax[i-1];
        for(int i=n;i;--i)sa[tax[rk[tp[i]]]--]=tp[i];
    }
    void Suffix(){ 
        for(int i=1;i<=n;++i)a[i]=s[i];
        for(int i=1;i<=n;++i)rk[i]=a[i],tp[i]=i;
        rsort();
        for(int w=1,p=1,i;p<n;w+=w,m=p){ 
            for(p=0,i=n-w+1;i<=n;++i)tp[++p]=i;
            for(i=1;i<=n;++i)if(sa[i]>w)tp[++p]=sa[i]-w;
            rsort(),swap(rk,tp),rk[sa[1]]=p=1;
            for(int i=2;i<=n;++i)
                rk[sa[i]]=cmp(tp,sa[i],sa[i-1],w)?p:++p;
        }
    }
    void getHt(){ 
        int j,k=0;
        for(int i=1;i<=n;ht[rk[i++]]=k)
            for(k=k?k-1:k,j=sa[rk[i]-1];a[i+k]==a[j+k];++k);
    }
    void init(){ 
        scanf("%s",s+1);
        n=strlen(s+1);
        m=(int)'z';
        Suffix();
        getHt();
    }
    ll solve(){ 
        ll ans=1ll*(n-1)*n*(n+1)/2;
        h=1,t=0;
        ht[n+1]=ht[0]=-1;
        for(int i=1;i<=n+1;++i){//左严格小于 右小于等于 
            while(h<=t&&ht[q[t]]>=ht[i])
                R[q[t--]]=i;
            q[++t]=i;
        }
        h=1,t=0;
        for(int i=n;i>=0;--i){ 
            while(h<=t&&ht[q[t]]>ht[i])
                L[q[t--]]=max(1,i);//数据水
            q[++t]=i;
        }
        for(int i=2;i<=n;++i){ 
            ans-=2ll*ht[i]*(i-L[i])*(R[i]-i);
        }
        return ans;
    }
}sa;
int main(){ 
    sa.init();
    cout<<sa.solve();
    return 0;
}

6.bzoj 4566 找相同字符

题意:

求两个字符串的相同子串数

思路:

转换为求第一个字符串所有后缀有多少个前缀和第二个字符串所有后缀的前缀相同,即把两个字符串拼起来后的两两 L C P LCP LCP之和,单调队列维护每个 h e i g h t height height的贡献区间,维护前缀1和2出现次数计算即可

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=4e5+5;
int q[maxn],h=1,t=0,L[maxn],R[maxn];
struct SA{ 
    char s[maxn];
    int n,m,a[maxn],sa[maxn],rk[maxn],ht[maxn],tax[maxn],tp[maxn],pos[maxn],sum1[maxn],sum2[maxn];
    int cmp(int*f,int x,int y,int w){ 
        return f[x]==f[y]&&f[x+w]==f[y+w];
    }
    void rsort(){ 
        for(int i=0;i<=m;++i)tax[i]=0;  
        for(int i=1;i<=n;++i)tax[rk[tp[i]]]++;
        for(int i=1;i<=m;++i)tax[i]+=tax[i-1];
        for(int i=n;i>=1;--i)sa[tax[rk[tp[i]]]--]=tp[i];
    }
    void Suffix(){ 
        for(int i=1;i<=n;++i)rk[i]=a[i],tp[i]=i;
        rsort();
        for(int w=1,p=1,i;p<n;w+=w,m=p){ 
            for(p=0,i=n-w+1;i<=n;++i)tp[++p]=i;
            for(i=1;i<=n;++i)if(sa[i]>w)tp[++p]=sa[i]-w;
            rsort(),swap(rk,tp),rk[sa[1]]=p=1;
            for(int i=2;i<=n;++i)
                rk[sa[i]]=cmp(tp,sa[i],sa[i-1],w)?p:++p;
        }
    }
    void getht(){ 
        int j,k=0;
        for(int i=1;i<=n;ht[rk[i++]]=k)
            for(k=k?k-1:k,j=sa[rk[i]-1];a[i+k]==a[j+k];++k);
    }    
    void init(){
        n=0; 
        for(int i=1;i<=2;++i){ 
            scanf("%s",s+1);
            int len=strlen(s+1);
            for(int j=1;j<=len;++j){ 
                a[++n]=s[j],pos[n]=i;
            }
            a[++n]='#'+i;
        }
        n--;
        m=(int)'z';
        Suffix();
        getht();
    }
    ll solve(){ 
        ll ans=0;
        for(int i=1;i<=n;++i){ 
            sum1[i]=sum1[i-1]+(pos[sa[i]]==1);//按排名的pos
            sum2[i]=sum2[i-1]+(pos[sa[i]]==2);
        }
        ht[n+1]=ht[0]=-1;   
        for(int i=1;i<=n+1;++i){ 
            while(h<=t&&ht[q[t]]>=ht[i])
                R[q[t--]]=i;
            q[++t]=i;
        }
        h=1,t=0;
        for(int i=n;i>=0;--i){ 
            while(h<=t&&ht[q[t]]>ht[i])
                L[q[t--]]=max(1,i);
            q[++t]=i;
        }
        /*for(int i=1;i<=n;++i){ 
            cout<<ht[i]<<" "<<L[i]<<" "<<R[i]<<" "<<pos[sa[i]]<<" "<<sum1[i]<<" "<<sum2[i]<<"\n";
        }*/
        for(int i=2;i<=n;++i){ 
            ans+=1ll*ht[i]*(sum1[R[i]-1]-sum1[i-1])*(sum2[i-1]-sum2[L[i]-1]);
            ans+=1ll*ht[i]*(sum2[R[i]-1]-sum2[i-1])*(sum1[i-1]-sum1[L[i]-1]);
        }
        return ans;
    }
}sa;
int main(){ 
    sa.init();
    cout<<sa.solve()<<"\n";
    return 0;
}

7.bzoj 2754 喵星球上的点名

题意:

姓名两种字符串,m次点名,若点名是其子串统计每次点名的时候有多少喵星人答到,以及 m 次点名结束后每个喵星人答到多少次吗

思路:

遇到多文本串问题,可以考虑转为在ac自动机上解决或者用后缀数组拼接解决。但这里字符集过大,用ac自动机的话得可持久化维护才可以做到 O ( n l o g n ) O(nlogn) O(nlogn),网上使用ac自动机 m a p map map暴力跳 f a i l fail fail的复杂度是 O ( M m i n ( S , M ) ) O(Mmin(\sqrt S,M)) O(Mmin(S ,M))的,在本题虽然可以过,但我们不使用这种方法,遇到如此大的字符集可以考虑主席树维护ac自动机,或者后缀数组。

我们把所有串拼在一起,后缀排序后,如果一个询问串 j j j是另一个后缀 i i i的子串的话,则 L C P ( i , j ) > = l e n ( j ) LCP(i,j)>=len(j) LCP(i,j)>=len(j)

所以满足条件的必定是连续的区间,我们可以 s t 表 st表 st加二分求出这个区间。

第一问就变成了区间不同种类数的问题,第二问问 m m m个区间中,每个种类的数出现了多少次(不重)。

S A + 莫 队 SA+莫队 SA+ S A + 树 状 数 组 都 可 以 解 SA+树状数组都可以解 SA+

莫队第一问用桶维护即可,第二问可以考虑差分,第一次遇到一类数,加上剩余询问,删除一种,减去剩余询问

树状数组第一问典中典不讲了,第二问我们可以考虑单独位置的贡献,每次查看当前颜色的位置和上一次出现的位置出现的左区间的数量即可, O ( n l o g n ) O(nlogn) O(nlogn)

#include<bits/stdc++.h>
using namespace std;
const int maxn=4e5+100;
const int maxm=1e5+5;
int M=1e4;
struct SA{
    struct Q{ 
        int l,r,id;
        bool operator<(const Q&a)const{ 
            return r<a.r;
        }
    }q[maxm];
    struct BIT{ 
        int M;
        #define lowb(i) (i&(-i))
        int c[maxn];
        void init(int x){ M=x;}
        void add(int x,int val){ 
            for(int i=x;i<=M;i+=lowb(i))
                c[i]+=val;
        }
        int ask(int x){ 
            int ans=0;
            for(int i=x;i;i-=lowb(i))
                ans+=c[i];
            return ans;
        }
    }bit;
    char s[maxn];
    int n,m,a[maxn],sa[maxn],rk[maxn],ht[maxn],tax[maxn],tp[maxn],pos[maxn],qu,len[maxm];
    int ans1[maxm],ans2[maxm],pre[maxn],col[maxm],num1,num2,mn[maxn][23],Log[maxn],head[maxm];
    int cmp(int*f,int x,int y,int w){ 
        return f[x]==f[y]&&f[x+w]==f[y+w];
    }
    void rsort(){ 
        for(int i=0;i<=m;++i)tax[i]=0;  
        for(int i=1;i<=n;++i)tax[rk[tp[i]]]++;
        for(int i=1;i<=m;++i)tax[i]+=tax[i-1];
        for(int i=n;i>=1;--i)sa[tax[rk[tp[i]]]--]=tp[i];
    }
    void Suffix(){ 
        for(int i=1;i<=n;++i)rk[i]=a[i],tp[i]=i;
        rsort();
        for(int w=1,p=1,i;p<n;w+=w,m=p){ 
            for(p=0,i=n-w+1;i<=n;++i)tp[++p]=i;
            for(i=1;i<=n;++i)if(sa[i]>w)tp[++p]=sa[i]-w;
            rsort(),swap(rk,tp),rk[sa[1]]=p=1;
            for(int i=2;i<=n;++i)
                rk[sa[i]]=cmp(tp,sa[i],sa[i-1],w)?p:++p;
        }
    }
    void getht(){ 
        int j,k=0;
        for(int i=1;i<=n;ht[rk[i++]]=k)
            for(k=k?k-1:k,j=sa[rk[i]-1];a[i+k]==a[j+k];++k);
    }    
    void getlr(){ 
        for(int i=1;i<=num2;++i){ 
            int lim=len[i],ps=rk[head[i]];
            int l=0,r=ps;
            bool f=0;
            while(l<r){ 
                int mid=(l+r+1)>>1;
                if(query(mid,ps)<lim){ 
                    l=mid,f=1;
                }else
                    r=mid-1;
            }
            q[i].l=(f?l:1);
            l=ps+1,r=n+1;
            f=0;
            while(l<r){ 
                int mid=l+r>>1;
                if(query(ps,mid)<lim){ 
                    r=mid;f=1;
                }else
                    l=mid+1;
            }
            q[i].r=(f?r-1:n);
        }
        for(int i=1;i<=num2;++i)q[i].id=i,bit.add(q[i].l,1);
        for(int i=1;i<=n;++i){ 
            int ps=pos[sa[i]];
            if(ps<=0)continue;
            if(!col[ps])col[ps]=i;
            else pre[i]=col[ps],col[ps]=i;
        }
    }
    void RMQ(){ 
        for(int i=1;i<=n;++i)mn[i][0]=ht[i];
        for(int j=1;(1<<j)<=n;++j){ 
            for(int i=1;i+(1<<j)-1<=n;++i)
                mn[i][j]=min(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
        }
        Log[1]=0;
        for(int i=2;i<=n+1;++i)
            Log[i]=Log[i/2]+1;
    }
    int query(int l,int r){ 
        int k=Log[r-l+1];
        return min(mn[l][k],mn[r-(1<<k)+1][k]);
    }
    void solve(){ 
        sort(q+1,q+1+num2);
        int now=1;
        for(int i=1;i<=num2;++i){ 
            while(now<=q[i].r){ 
                if(pos[sa[now]]<=0){ 
                    now++;continue;
                }
                ans2[pos[sa[now]]]+=bit.ask(now)-bit.ask(pre[now]);
                now++;
            }       
            bit.add(q[i].l,-1);
        }
        now=1;
        for(int i=1;i<=num2;++i){ 
            while(now<=q[i].r){ 
                if(pos[sa[now]]<=0){ 
                    now++;continue;
                }
                if(!pre[now])bit.add(now,1);
                else bit.add(pre[now],-1),bit.add(now,1);
                now++;
            }
            ans1[q[i].id]=bit.ask(q[i].r)-bit.ask(q[i].l-1);
        }
    }
    void init(){ 
        int x,c;
        scanf("%d%d",&num1,&num2);
        for(int i=1;i<=num1;++i){ 
            scanf("%d",&x);
            for(int j=1;j<=x;++j){ 
                scanf("%d",&c);
                a[++n]=c;pos[n]=i;
            }
            a[++n]=++M;
            scanf("%d",&x);
            for(int j=1;j<=x;++j){ 
                scanf("%d",&c);
                a[++n]=c;pos[n]=i;
            }
            a[++n]=++M;
        }
        for(int i=1;i<=num2;++i){ 
            scanf("%d",&len[i]);
            head[i]=n+1;
            for(int j=1;j<=len[i];++j){ 
                scanf("%d",&c);
                a[++n]=c;pos[n]=-i;
            }
            a[++n]=++M;
        }
        bit.init(n);
        m=M+1;
        Suffix();
        getht();
        RMQ();
        getlr();
        solve();
        for(int i=1;i<=num2;++i)cout<<ans1[i]<<"\n";
        for(int i=1;i<=num1;++i)cout<<ans2[i]<<" ";
    }
}sa;
int main(){ 
    sa.init();
    return 0;
}

8.bzoj 1031 字符加密

题意:字符环状后按字典序排列的第 n n n个字符排列输出

思路:

复制一段到后面跑后缀数组即可,对于任意两个后缀,若前 n n n位不同,不会有影响,若相同,由于只输出第 n n n位,也没有关系,所以总体相对顺序不会影响, S A I S SAIS SAIS下时间复杂度为 O ( n ) O(n) O(n)

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
struct SA{ 
    char s[maxn];
    int n,m,a[maxn],sa[maxn],rk[maxn],ht[maxn],tax[maxn],tp[maxn];
    int cmp(int*f,int x,int y,int w){ 
        return f[x]==f[y]&&f[x+w]==f[y+w];
    }
    void rsort(){ 
        for(int i=0;i<=m;++i)tax[i]=0;  
        for(int i=1;i<=n;++i)tax[rk[tp[i]]]++;
        for(int i=1;i<=m;++i)tax[i]+=tax[i-1];
        for(int i=n;i>=1;--i)sa[tax[rk[tp[i]]]--]=tp[i];
    }
    void Suffix(){ 
        for(int i=1;i<=n;++i)a[i]=s[i];
        for(int i=1;i<=n;++i)rk[i]=a[i],tp[i]=i;
        rsort();
        for(int w=1,p=1,i;p<n;w+=w,m=p){ 
            for(p=0,i=n-w+1;i<=n;++i)tp[++p]=i;
            for(i=1;i<=n;++i)if(sa[i]>w)tp[++p]=sa[i]-w;
            rsort(),swap(rk,tp),rk[sa[1]]=p=1;
            for(int i=2;i<=n;++i)
                rk[sa[i]]=cmp(tp,sa[i],sa[i-1],w)?p:++p;
        }
    }
    void solve(){ 
        scanf("%s",s+1);
        n=2*strlen(s+1);
        int len=strlen(s+1)+1;
        for(int i=len;i<=n;++i){ 
            s[i]=s[i-len+1];
        }
        //for(int i=1;i<=n;++i)
          //  putchar(s[i]);
        m=200;//最大字符ASCII码
        Suffix();
        for(int i=1;i<=n;++i)if(sa[i]<=len-1)putchar(s[sa[i]+len-2]);
    }
}sa;
int main(){ 
    sa.solve();
    return 0;
}


9.bzoj 4650 优秀的拆分

题意:

问一个字符串有多少个子串是形如 A A B B AABB AABB这种形式的

思路:

神仙题完全不知道怎么想到的,设 f [ i ] f[i] f[i] g [ i ] g[i] g[i]表示以 i i i结尾的 A A AA AA形式数量, g [ i ] g[i] g[i]表示以 i i i为开头的 A A AA AA形式数量,答案就是 ∑ f [ i ] g [ i + 1 ] \sum f[i]g[i+1] f[i]g[i+1]

接下来就是神仙思路了,我们考虑枚举 A A A的长度len,每len个长度设下一个关键点,那么每个合法的 A A AA AA必定经过2个关键点,那么只要求出这两个点往后的 L C P LCP LCP和往前的 L C S LCS LCS, L C P + L C S > = l e n LCP+LCS>=len LCP+LCS>=len的情况下必定有解,我们这个AA串的终点落在中间长度为 L c p + L c s − l e n + 1 Lcp+Lcs-len+1 Lcp+Lcslen+1的交上都是可以的,注意 L C P 和 L C S LCP和LCS LCPLCS不能超过当前长度。

因为这样的话平移一下可以保证紧跟着出现一个不重叠的AA串

又因为串AA起点和终点分别出现的位置是一段区间,所以分别在 f f f g g g上差分接口,细节巨多,复杂度是调和级数 O ( n l o g n ) O(nlogn) O(nlogn)

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int maxn=3e4+5;
int Log[maxn];
struct SA{ 
    char s[maxn];
    int n,m,a[maxn],sa[maxn],rk[maxn],ht[maxn],tax[maxn],tp[maxn],mn[maxn][17];
    int cmp(int*f,int x,int y,int w){ 
        return f[x]==f[y]&&f[x+w]==f[y+w];
    }
    void rsort(){ 
        for(int i=0;i<=m;++i)tax[i]=0;  
        for(int i=1;i<=n;++i)tax[rk[tp[i]]]++;
        for(int i=1;i<=m;++i)tax[i]+=tax[i-1];
        for(int i=n;i>=1;--i)sa[tax[rk[tp[i]]]--]=tp[i];
    }
    void Suffix(){ 
        for(int i=1;i<=n;++i)a[i]=s[i];
        for(int i=1;i<=n;++i)rk[i]=a[i],tp[i]=i;
        rsort();
        for(int w=1,p=1,i;p<n;w+=w,m=p){ 
            for(p=0,i=n-w+1;i<=n;++i)tp[++p]=i;
            for(i=1;i<=n;++i)if(sa[i]>w)tp[++p]=sa[i]-w;
            rsort(),swap(rk,tp),rk[sa[1]]=p=1;
            for(int i=2;i<=n;++i)
                rk[sa[i]]=cmp(tp,sa[i],sa[i-1],w)?p:++p;
        }
    }
    void init(){ 
        memset(mn,0,sizeof(mn));
        for(int i=0;i<=n;++i)a[i]=sa[i]=rk[i]=ht[i]=tax[i]=tp[i]=0;
    }
    void getht(){ 
        int j,k=0;
        for(int i=1;i<=n;ht[rk[i++]]=k)
            for(k=k?k-1:k,j=sa[rk[i]-1];a[i+k]==a[j+k];++k);
    }    
    void RMQ(){ 
        for(int i=1;i<=n;++i)mn[i][0]=ht[i];
        for(int j=1;(1<<j)<=n;++j)
            for(int i=1;i+(1<<j)-1<=n;++i)
                mn[i][j]=min(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
    }
    void solve(int x){ 
        n=x;//结束符!不能直接strlen
        m=(int)'z';//最大字符ASCII码
        Suffix();
        getht();
        RMQ();
    }
    int query(int l,int r){ 
        l=rk[l],r=rk[r];
        if(l>r)swap(l,r);
        l++;
        int k=Log[r-l+1];
        return min(mn[l][k],mn[r-(1<<k)+1][k]);
    }
}pre,suf;
int f[maxn],g[maxn];
int main(){ 
    Log[1]=0;
    for(int i=2;i<maxn;++i)
        Log[i]=Log[i/2]+1;
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%s",suf.s+1);
        int len=strlen(suf.s+1);
        for(int i=1;i<=len;++i)pre.s[i]=suf.s[len+1-i]; 
        pre.solve(len);suf.solve(len);
        for(int i=1;i<=len/2;++i){ 
            for(int j=1;j+i<=len;j+=i){ 
                int fi=j,se=j+i;
                int lcp=min(suf.query(fi,se),i),lcs=min(pre.query(len+1-se,len+1-fi),i);//原序列和排序后的序列分清楚!
                int del=lcp+lcs-i;
                if(del>0){ 
                    f[se+lcp-del]++;f[se+lcp]--;
                    g[fi-lcs+del+1]--;g[fi-lcs+1]++;
                }
            }
        }
        ll ans=0;
        for(int i=2;i<=len;++i)
            f[i]+=f[i-1],g[i]+=g[i-1];
        for(int i=1;i<len;++i)
            ans+=(ll)f[i]*g[i+1];
        pre.init();suf.init();
        for(int i=0;i<=len+1;++i)f[i]=g[i]=0;//差分到len+1可能有影响
        cout<<ans<<"\n";
    }
    return 0;
}

10.bzoj 4199 品酒大会

题意:

∀ i ∈ [ 0 , n ) ∀i∈[0,n) i[0,n)求有多少对后缀满足 l e n ( l c p ) > = i len(lcp)>=i len(lcp)>=i以及满足条件的两个后缀的权值乘积的最大值

思路:

套路题,两两之间后缀考虑单调队列维护 h e i g h t height height贡献,差分即可,乘积用 s t st st表维护最大最小值,然后求个后缀最大值就秒了,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=3e5+5;
struct SA{ 
    int s[(maxn<<1)+5],sa[maxn+5],rk[maxn+5],c[maxn+5],p[maxn+5],tmp[maxn+5],n,m,ht[maxn];
    int L[maxn],R[maxn],q[maxn],h1,t1,a[maxn],mn[maxn][21],mx[maxn][21],Log[maxn];
    char str[maxn+5]; bool t[(maxn<<1)+5];
    ll ans1[maxn],ans2[maxn];
    #define Ar(x,a) sa[p[s[x]]a]=x
    void IS(int*s,int*s1,int n1,int n,int m,bool*t){
	    memset(sa+1,0,n<<2);memset(c+1,0,m<<2);
        for(int i=1;i<=n;++c[s[i++]]);
	    for(int i=2;i<=m;i++) c[i]+=c[i-1]; 
        memcpy(p+1,c+1,m<<2); 
        for(int i=n1;i;Ar(s1[i],--),i--);
	    for(int i=1;i<=m;i++) p[i]=c[i-1]+1; 
        for(int i=1;i<=n;sa[i]>1&&t[sa[i]-1]?Ar(sa[i]-1,++):0,i++);
	    memcpy(p+1,c+1,m<<2);
        for(int i=n;i;sa[i]>1&&!t[sa[i]-1]?Ar(sa[i]-1,--):0,i--);
    }
    void SAIS(int s[], bool t[], int tmp[], int n, int m){
	    int n1=0,m1=rk[1]=0,*s1=s+n;t[n]=0;
	    for(int i=n-1;i;t[i]=s[i]^s[i+1]?s[i]>s[i+1]:t[i+1],i--);
	    for(int i=2;i<=n;rk[i]=t[i-1]&&!t[i]?tmp[++n1]=i,n1:0,i++); 
        IS(s,tmp,n1,n,m,t);
	    for(int i=1,x,y=0;i<=n;i++) if(x=rk[sa[i]]){
		    if(m1<=1||tmp[x+1]-tmp[x]!=tmp[y+1]-tmp[y]) ++m1;
		    else for(int a=tmp[x],b=tmp[y];a<=tmp[x+1];a++,b++) if((s[a]<<1|t[a])^(s[b]<<1|t[b])){++m1;break;}
		    s1[y=x]=m1;
	    } 
	    if(m1<n1) SAIS(s1,t+n,tmp+n1,n1,m1); 
	    else for(int i=1;i<=n1;sa[s1[i]]=i,i++);
	    for(int i=1;i<=n1;s1[i]=tmp[sa[i]],i++); 
	    IS(s,s1,n1,n,m,t);
    }
    void MakeSA(){ 
    	--n; 
        for(int i=1;i<=n;i++) sa[i]=sa[i+1],rk[sa[i]]=i;
    }
    void getHt(){
	    for(int i=1,k=0;i<=n;ht[rk[i]]=k,i++,k&&--k) 
	    	for(int j=sa[rk[i]-1];s[i+k]==s[j+k];k++);
    }
    void RMQ(){ 
        for(int i=1;i<=n;++i)mn[i][0]=mx[i][0]=a[sa[i]];
        for(int j=1;(1<<j)<=n;++j)
            for(int i=1;i+(1<<j)-1<=n;++i){ 
                mn[i][j]=min(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
                mx[i][j]=max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]);
            }
        Log[1]=0;
        for(int i=2;i<=n+1;++i)
            Log[i]=Log[i/2]+1;
    }
    int queryMx(int l,int r){ 
        int k=Log[r-l+1];
        return max(mx[l][k],mx[r-(1<<k)+1][k]);
    }
    int queryMn(int l,int r){ 
        int k=Log[r-l+1];
        return min(mn[l][k],mn[r-(1<<k)+1][k]);
    }
    inline int read(){ 
        char c=getchar();int x=0,s=1;
        while(c<'0'||c>'9'){ if(c=='-')s=-1;c=getchar();}
        while(c>='0'&&c<='9'){ x=x*10+c-'0';c=getchar();}
        return x*s;
    }
    void init(){ 
        scanf("%d",&n);
        scanf("%s",str+1);
        for(int i=1;i<=n;++i)a[i]=read();
        for(int i=1;i<=n;i++) s[i]=str[i];
	    s[++n]=1; //小于所有字符集
        SAIS(s,t,tmp,n,'z'+1); //最大字符集+1
        MakeSA();
        getHt();
        RMQ();
    }
    void solve(){ 
        h1=1;t1=0;
        for(int i=1;i<=n;++i){ 
            while(h1<=t1&&ht[q[t1]]>=ht[i])
                R[q[t1--]]=i;
            L[i]=max(1,q[t1]);
            q[++t1]=i;
        }
        while(h1<=t1)R[q[t1--]]=n+1;
        for(int i=1;i<=n;++i)ans2[i]=-9e18;
        for(int i=2;i<=n;++i){ 
            ll del=(ll)(i-L[i])*(R[i]-i);
            ans1[0]+=del;ans1[ht[i]+1]-=del;
            int mnL=queryMn(L[i],i-1),mnR=queryMn(i,R[i]-1);
            int mxL=queryMx(L[i],i-1),mxR=queryMx(i,R[i]-1);
            ans2[ht[i]]=max(ans2[ht[i]],max((ll)mnL*mnR,(ll)mxL*mxR));
        }
        for(int i=1;i<=n-1;++i)
            ans1[i]+=ans1[i-1];
        for(int i=n-2;i>=0;--i)
            ans2[i]=max(ans2[i],ans2[i+1]);
        for(int i=0;i<=n-1;++i)
            cout<<ans1[i]<<" "<<(ans1[i]?ans2[i]:0)<<"\n";
    }
}sa;
int main(){
    sa.init();
    sa.solve();
}

11.bzoj 4556 字符串

题意:

m 次 m次 m询问,每次问属于 s [ a . . b ] 的 所 有 子 串 和 s [ c . . d ] s[a..b]的所有子串和s[c..d] s[a..b]s[c..d]的最大的lcp,所有子串加上 l c p lcp lcp,考虑后缀数组,我们二分答案 l e n len len,即满足某个后缀j, l c p ( j , c ) > = l e n lcp(j,c)>=len lcp(j,c)>=len,且首字母出现在 [ a , b − l e n + 1 ] [a,b-len+1] [a,blen+1]中, l c p lcp lcp是单峰的,所有第一个满足的必定是一段连续区间,用 s t 表 + 二 分 st表+二分 st+即可解决,第二个用主席树维护 s a [ i ] sa[i] sa[i]即可,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

#include <bits/stdc++.h>
#define ls lc[p]
#define rs rc[p]
#define lson lc[p],l,mid
#define rson rc[p],mid+1,r
using namespace std;
const int maxn=1e5+5;
const int N=maxn*32;
int tot,lc[N],rc[N],sum[N],rt[maxn],a,b,c1,d,q;
void pushUp(int p){ 
    sum[p]=sum[ls]+sum[rs];
}
void update(int q,int&p,int l,int r,int x){ 
    p=++tot;
    ls=lc[q],rs=rc[q],sum[p]=sum[q];
    if(l==r){ 
        sum[p]++;return;
    }
    int mid=l+r>>1;
    if(x<=mid)update(lc[q],lson,x);
    else update(rc[q],rson,x);
    pushUp(p);
}
int query(int q,int p,int l,int r,int L,int R){ 
    if(L<=l&&r<=R)return sum[p]-sum[q];
    int mid=l+r>>1,ans=0;
    if(L<=mid)ans+=query(lc[q],lson,L,R);
    if(R>mid)ans+=query(rc[q],rson,L,R);
    return ans;
}
struct SA{ 
    int s[(maxn<<1)+5],sa[maxn+5],rk[maxn+5],c[maxn+5],p[maxn+5],tmp[maxn+5],n,m,ht[maxn];
    char str[maxn+5]; bool t[(maxn<<1)+5];
    int mn[maxn][18],Log[maxn];
    #define Ar(x,a) sa[p[s[x]]a]=x
    void IS(int*s,int*s1,int n1,int n,int m,bool*t){
	    memset(sa+1,0,n<<2);memset(c+1,0,m<<2);
        for(int i=1;i<=n;++c[s[i++]]);
	    for(int i=2;i<=m;i++) c[i]+=c[i-1]; 
        memcpy(p+1,c+1,m<<2); 
        for(int i=n1;i;Ar(s1[i],--),i--);
	    for(int i=1;i<=m;i++) p[i]=c[i-1]+1; 
        for(int i=1;i<=n;sa[i]>1&&t[sa[i]-1]?Ar(sa[i]-1,++):0,i++);
	    memcpy(p+1,c+1,m<<2);
        for(int i=n;i;sa[i]>1&&!t[sa[i]-1]?Ar(sa[i]-1,--):0,i--);
    }
    void SAIS(int s[], bool t[], int tmp[], int n, int m){
	    int n1=0,m1=rk[1]=0,*s1=s+n;t[n]=0;
	    for(int i=n-1;i;t[i]=s[i]^s[i+1]?s[i]>s[i+1]:t[i+1],i--);
	    for(int i=2;i<=n;rk[i]=t[i-1]&&!t[i]?tmp[++n1]=i,n1:0,i++); 
        IS(s,tmp,n1,n,m,t);
	    for(int i=1,x,y=0;i<=n;i++) if(x=rk[sa[i]]){
		    if(m1<=1||tmp[x+1]-tmp[x]!=tmp[y+1]-tmp[y]) ++m1;
		    else for(int a=tmp[x],b=tmp[y];a<=tmp[x+1];a++,b++) if((s[a]<<1|t[a])^(s[b]<<1|t[b])){++m1;break;}
		    s1[y=x]=m1;
	    } 
	    if(m1<n1) SAIS(s1,t+n,tmp+n1,n1,m1); 
	    else for(int i=1;i<=n1;sa[s1[i]]=i,i++);
	    for(int i=1;i<=n1;s1[i]=tmp[sa[i]],i++); 
	    IS(s,s1,n1,n,m,t);
    }
    void MakeSA(){ 
    	--n; 
        for(int i=1;i<=n;i++) sa[i]=sa[i+1],rk[sa[i]]=i;
    }
    void getHt(){
	    for(int i=1,k=0;i<=n;ht[rk[i]]=k,i++,k&&--k) 
	    	for(int j=sa[rk[i]-1];s[i+k]==s[j+k];k++);
    }
    void RMQ(){ 
        for(int i=1;i<=n;++i)mn[i][0]=ht[i];
        for(int j=1;(1<<j)<=n;++j)
            for(int i=1;i+(1<<j)-1<=n;++i)
                mn[i][j]=min(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
        Log[1]=0;
        for(int i=2;i<=n+1;++i)
            Log[i]=Log[i/2]+1;
    }
    int queryMn(int l,int r){ 
        if(l>r)swap(l,r);
        l++;
        int k=Log[r-l+1];
        return min(mn[l][k],mn[r-(1<<k)+1][k]);
    }
    void init(){ 
        scanf("%d%d",&n,&q);
        scanf("%s",str+1); 
        for(int i=1;i<=n;i++) s[i]=str[i];
	    s[++n]=1; //小于所有字符集
        SAIS(s,t,tmp,n,'z'+1); //最大字符集+1
        MakeSA();
        getHt();
        RMQ();
        for(int i=1;i<=n;++i)update(rt[i-1],rt[i],1,n,sa[i]);
    }
    bool check(int x){ 
        int ps=rk[c1];
        int l1=1,r1=ps;
        while(l1<r1){ 
            int mid=l1+r1>>1;
            if(queryMn(mid,ps)>=x)
                r1=mid;
            else l1=mid+1;
        }
        int l2=ps,r2=n;
        while(l2<r2){ 
            int mid=(l2+r2+1)>>1;
            if(queryMn(ps,mid)>=x)
                l2=mid;
            else r2=mid-1;
        }
        int L=r1,R=l2;
        return query(rt[max(0,L-1)],rt[R],1,n,a,b-x+1)>0;
    }
    void solve(){ 
        for(int i=1;i<=q;++i){ 
            scanf("%d%d%d%d",&a,&b,&c1,&d);
            int l=0,r=min(b-a+1,d-c1+1);
            while(l<r){ 
                int mid=(l+r+1)>>1;
                if(check(mid))l=mid;
                else r=mid-1;
            }        
            cout<<l<<"\n";
        }    
    }
}sa;
int main(){
    sa.init();
    sa.solve();
}


12.bzoj 4516 生成魔咒

题意:在结尾动态插入字符,每次插入结束后输出当前串中本质不同的字串个数

思路:

这里提供一种离线思路,在线等SAM的时候会补,考虑翻转加,则每次只增加一个后缀串,序列插入问题可以考虑转化为反向删除问题,每次删除一个都把影响到的两个后缀的贡献删去,重新维护 h e i g h t height height,这样我们就可以用一个链表维护这个了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
struct SA{ 
    int s[maxn],d[maxn],u[maxn],b[maxn];
    int n,m,a[maxn],sa[maxn],rk[maxn],ht[maxn],tax[maxn],tp[maxn];
    ll ans[maxn];
    int cmp(int*f,int x,int y,int w){ 
        return f[x]==f[y]&&f[x+w]==f[y+w];
    }
    void rsort(){ 
        for(int i=0;i<=m;++i)tax[i]=0;  
        for(int i=1;i<=n;++i)tax[rk[tp[i]]]++;
        for(int i=1;i<=m;++i)tax[i]+=tax[i-1];
        for(int i=n;i>=1;--i)sa[tax[rk[tp[i]]]--]=tp[i];
    }
    void Suffix(){ 
        for(int i=1;i<=n;++i)rk[i]=a[i],tp[i]=i;
        rsort();
        for(int w=1,p=1,i;p<n;w+=w,m=p){ 
            for(p=0,i=n-w+1;i<=n;++i)tp[++p]=i;
            for(i=1;i<=n;++i)if(sa[i]>w)tp[++p]=sa[i]-w;
            rsort(),swap(rk,tp),rk[sa[1]]=p=1;
            for(int i=2;i<=n;++i)
                rk[sa[i]]=cmp(tp,sa[i],sa[i-1],w)?p:++p;
        }
    }
    void getht(){ 
        int j,k=0;
        for(int i=1;i<=n;ht[rk[i++]]=k)
            for(k=k?k-1:k,j=sa[rk[i]-1];a[i+k]==a[j+k];++k);
    }    
    void init(){ 
        scanf("%d",&n);
        for(int i=n;i;--i)scanf("%d",&a[i]),b[i]=a[i];
        sort(b+1,b+1+n);
        m=unique(b+1,b+1+n)-(b+1);
        for(int i=1;i<=n;++i)a[i]=lower_bound(b+1,b+1+m,a[i])-b;
        Suffix();
        getht();
    }
    void solve(){ 
        for(int i=1;i<=n;++i)u[i]=i-1,d[i]=i+1;
        ll res=0;
        for(int i=1;i<=n;++i)
            res+=n+1-sa[i]-ht[i];
        for(int i=1;i<=n;++i){ 
            ans[i]=res;
            int pos=rk[i],j=d[pos];
            res-=n+1-sa[pos]-ht[pos];
            res-=n+1-sa[j]-ht[j];
            ht[j]=min(ht[j],ht[pos]);
            res+=n+1-sa[j]-ht[j];
            d[u[pos]]=j,u[j]=u[pos];
        }
        for(int i=n;i;--i)cout<<ans[i]<<"\n";

    }
}sa;
int main(){
    sa.init();
    sa.solve();
}

下面总结的常用的应用以及套路

一.单字符串

1.最长公共前缀

题意: 给定一个字符串,询问某两个后缀的最长公共前缀。

思路:求出height数组后,线段树或 s t st st表排序即可

2.可重叠最长重复子串

题意: 给定一个字符串,求最长重复子串,这两个子串可以重叠。

思路: h e i g h t height height的最大值

3.不可重叠最长重复子串

题意:给定一个字符串,求最长不可重复子串,这两个子串不可以重叠。

思路:二分+height分组,只要存在 m a x ( s a [ i ] ) − m i n ( s a [ i ] ) > = l e n max(sa[i])-min(sa[i])>=len max(sa[i])min(sa[i])>=len即可

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值