后缀数组suffix array

自用模板:

sa:字典序中排第i位的起使位置在str中第sa[i],sa[1,n]
rk:str第i个位置的后缀在字典序中排第几,rk[1,n]
height:字典序排i和i-1的后缀的最长公共前缀,height[2,n]

#include<bits/stdc++.h>
#define maxn 1000050
using namespace std;
char s[maxn];
int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn];
int n,m;

inline void get_SA() 
{
    for(int i=1;i<=n;++i) ++c[x[i]=s[i]];
    //c数组是桶
    //x[i]是第i个元素的第一关键字
    for(int i=2;i<=m;++i) c[i]+=c[i-1];
    //做c的前缀和,我们就可以得出每个关键字最多是在第几名
    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;
        //y[i]表示第二关键字排名为i的数,第一关键字的位置
        //第n-k+1到第n位是没有第二关键字的 所以排名在最前面
        for(int i=1;i<=n;++i) if(sa[i]>k) y[++num]=sa[i]-k;
        //排名为i的数 在数组中是否在第k位以后
        //如果满足(sa[i]>k) 那么它可以作为别人的第二关键字,就把它的第一关键字的位置添加进y就行了
        //所以i枚举的是第二关键字的排名,第二关键字靠前的先入队
        for(int i=1;i<=m;++i) c[i]=0;
        //初始化c桶
        for(int i=1;i<=n;++i) ++c[x[i]];
        //因为上一次循环已经算出了这次的第一关键字 所以直接加就行了
        for(int i=2;i<=m;++i) c[i]+=c[i-1]; //第一关键字排名为1~i的数有多少个
        for(int i=n;i>=1;--i) sa[c[x[y[i]]]--]=y[i],y[i]=0;
        //因为y的顺序是按照第二关键字的顺序来排的
        //第二关键字靠后的,在同一个第一关键字桶中排名越靠后
        //基数排序
        swap(x,y);
        //这里不用想太多,因为要生成新的x时要用到旧的,就把旧的复制下来,没别的意思
        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;
        //因为sa[i]已经排好序了,所以可以按排名枚举,生成下一次的第一关键字
        if(num==n) break;
        m=num;
        //这里就不用那个m了,因为都有新的编号了
    }
}
inline void get_height() 
{
    int k=0;
    for(int i=1;i<=n;++i) rk[sa[i]]=i;
    for(int i=1;i<=n;++i) 
    {
        if(rk[i]==1) continue;//第一名height为0
        if(k) --k;//h[i]>=h[i-1]-1;
        int j=sa[rk[i]-1];
        while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k]) ++k;
        height[rk[i]]=k;//h[i]=height[rk[i]];
    }
}
int main() 
{

    return 0;        
}
View Code

 

这板子我能打错无数遍o(╥﹏╥)o

单字符串:

可重叠最长重复子串:height[i]的max

不可重叠最长重复子串(相似子串)

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e4+10;
char s[maxn];
int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn];
int n,m=150;

inline void get_SA()
{
    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; 
    }
}

inline void get_height()
{
    int k=0;
    for(int i=1;i<=n;i++)rk[sa[i]]=i;
    for(int i=1;i<=n;i++)
    {
        if(rk[i]==1)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;
    }
} 

bool judge(int num)
{
    int l=sa[1],r=sa[1];
    for(int i=2;i<=n;i++)
    {
        if(height[i]>=num)
        {
            l=min(l,sa[i]);
            r=max(r,sa[i]);
        }
        else
        {
            l=r=sa[i];
        }
        if(r-l>num)return true;
    }
    return false;
}

int main()
{
    scanf("%d",&n);
    int a[maxn]={0};
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        if(i!=1)s[i-1]=a[i]-a[i-1]+48;
    }
    n--;
    get_SA();
    get_height();
    
    int low=1,high=n,mid,ans=0;
    while(low<=high)
    {
        mid=(low+high)>>1;
        if(judge(mid))
        {
            ans=mid;
            low=mid+1;
        }
        else
        {
            high=mid-1;
        }
    }
    printf("%d\n",ans+1);
    return 0;
}
View Code

可重叠至少k次重复子串

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e4+10;
char s[maxn];
int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn];
int n,m=150,k;

inline void get_SA()
{
    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; 
    }
}

inline void get_height()
{
    int k=0;
    for(int i=1;i<=n;i++)rk[sa[i]]=i;
    for(int i=1;i<=n;i++)
    {
        if(rk[i]==1)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;
    }
}

bool judge(int num)
{
    int cnt=1;
    for(int i=2;i<=n;i++)
    {
        if(height[i]>=num)
        {
            cnt++;
        }
        else
        {
            cnt=1;
        }
        if(cnt>=k)return true;
    }
    return false;
}

int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    {
        int num;
        scanf("%d",&num);
        s[i]=num+48;
    }
    get_SA();
    get_height();
    int low=1,high=n,mid,ans=0;
    while(low<=high)
    {
        mid=(low+high)>>1;
        if(judge(mid))
        {
            ans=mid;
            low=mid+1;
        }
        else
        {
            high=mid-1;
        }
    }
    printf("%d\n",ans);
    return 0;
}
View Code

不同字串个数:对n-sa[i]+1-height[i]求和

求第k大子串左右端点,hdu5008:

预处理每个后缀里有多少子串,找出第k大的子串第一次出现的后缀,之后二分+rmq找左右端点

https://blog.csdn.net/weixin_43093481/article/details/82875115

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e5+5;
char s[maxn];
int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn];
int n,m=131;
ll sum[maxn],dp1[maxn][30],dp2[maxn][30];

inline void get_SA()
{
    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;
    }
}

inline void get_height()
{
    int k=0;
    for(int i=1;i<=n;i++)rk[sa[i]]=i;
    for(int i=1;i<=n;i++)
    {
        if(rk[i]==1)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 cal()
{
    sum[0]=0;
    for(int i=1;i<=n;i++)
    {
        sum[i]=sum[i-1]+n-sa[i]+1-height[i];
    }
    return;
}

void rmq_init()
{
    for(int i=1;i<=n;i++)
    {
        dp1[i][0]=height[i];
        dp2[i][0]=sa[i];
    }
    for(int j=1;(1<<j)<=n;j++)
    {
        for(int i=1;i+(1<<j)-1<=n;i++)
        {
            dp1[i][j]=min(dp1[i][j-1],dp1[i+(1<<j-1)][j-1]);
            dp2[i][j]=min(dp2[i][j-1],dp2[i+(1<<j-1)][j-1]);
        }
    }
}
ll rmq1(int l,int r)
{
    int k=log2(r-l+1);
    return min(dp1[l][k],dp1[r-(1<<k)+1][k]);
}

ll rmq2(int l,int r)
{
    int k=log2(r-l+1);
    return min(dp2[l][k],dp2[r-(1<<k)+1][k]);
}

int main()
{
    //freopen("1.txt","r",stdin);
    //freopen("2.txt","w",stdout); 
    scanf("%s",s+1);
    n=strlen(s+1);
    get_SA();
    get_height();
    cal();
    rmq_init();
    
    int q;
    ll lp=0,rp=0,k;
    scanf("%d",&q);
    while(q--)
    {
        scanf("%lld",&k);
        k^=lp,k^=rp,k++;
        if(k>sum[n])
        {
            lp=rp=0;
        }
        else
        {
            int pos=lower_bound(sum+1,sum+1+n,k)-sum;
            k-=sum[pos-1];
            ll len=height[pos]+k;
            int low=pos+1,high=n,mid,ans=pos;
            while(low<=high)
            {
                mid=(low+high)>>1;
                if(rmq1(pos+1,mid)>=len)
                {
                    ans=mid;
                    low=mid+1;
                }
                else
                {
                    high=mid-1; 
                }
            }
            ll tmp=rmq2(pos,ans);
            lp=tmp;
            rp=tmp+len-1;
        }
        printf("%lld %lld\n",lp,rp);
    }
    return 0; 
} 
View Code

不可重叠重复子串个数 

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+10;
char s[maxn];
int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn];
int n,m=131;

inline void get_SA()
{
    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; 
    }
}
inline void get_height()
{
    int k=0;
    for(int i=1;i<=n;i++)rk[sa[i]]=i;
    for(int i=1;i<=n;i++)
    {
        if(rk[i]==1)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 judge(int num)
{
    int l=sa[1],r=sa[1],cnt=0;
    for(int i=2;i<=n;i++)
    {
        if(height[i]>=num)
        {
            l=min(l,sa[i]);
            r=max(r,sa[i]);
        }
        else
        {
            if(r-l>=num)cnt++;
            l=r=sa[i];
        }
    }
    if(r-l>=num)cnt++;
    return cnt;
}

int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    get_SA();
    get_height();

    int ans=0;
    for(int i=1;i<=n/2;i++)
    {
        ans+=judge(i);
    }
    printf("%d\n",ans);
    return 0;
}
View Code

 

两个字符串:

最长公共子串:两串之间用‘$’连接

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
char s[maxn];
int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn];
int n,m=131;

inline void get_SA()
{
    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;
    }
}

inline void get_height()
{
    int k=0;
    for(int i=1;i<=n;i++)rk[sa[i]]=i;
    for(int i=1;i<=n;i++)
    {
        if(rk[i]==1)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()
{
    int n1,n2;
    scanf("%s",s+1);
    n1=strlen(s+1);
    s[n1+1]='$';
    scanf("%s",s+n1+2);
    n2=strlen(s+1)-n1-1;
    n=n1+n2+1;
    get_SA();
    get_height();
    
    int ans=0;
    for(int i=2;i<=n;i++)
    {
        if(height[i]>ans)
        {
            if(sa[i]<n1+1&&sa[i-1]>n1+1||sa[i]>n1+1&&sa[i-1]<n1+1)
            ans=height[i];
        }
    }
    printf("%d\n",ans);
    return 0;
}
View Code

长度不小于K的公共子串个数

 

多个字符串:

其他串没有的子串

多串的最长公共子串

不小于个串的最长子串

每个串中至少出现两次且不重叠的子串个数

出现或反转出现在每个字符串的最长子串

 

hdu5343 sam后缀自动机+记忆化搜索

这个专题好难,不知道啥时候补...

posted @ 2019-08-26 16:37  myrtle  阅读(265)  评论(0编辑  收藏  举报