Codeforces 128B string(后缀数组)

链接

题目大意:

给一个长度为n的字符串,求出这个字符串的所有子串(允许存在相同的子串)的字典序第k小是哪一个。(n<=1e5,k<=1e5)

题解:

因为每个后缀的前缀就是一个子串,先对这个串求后缀数组,维护当前子串排名nowrk,然后由rank从小到大枚举所有后缀,对于每一个后缀,可以从与前一个后缀的最长公共前缀+1开始暴力遍历它的前缀,求出后面的后缀与当前后缀的前缀的公共前缀的个数m(求m可以先预处理高度数组的rmq,根据height数组的性质二分),那么当前这个前缀代表的子串的排名就是nowrk+m+1。因为k<=1e5所以最多求1e5次就能得到答案复杂度O(nlogn)。

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=100005;
int sa[maxn],rk[maxn],fir[maxn],sec[maxn],tmp[maxn],buckt[maxn];
int a[maxn],b[maxn],height[maxn];
char s[maxn];
void SA(int n)
{
    for(int i = 0;i < n;++i)
    a[i+1]=b[i+1]=s[i];
    sort(b+1,b+1+n);
    int cnt=unique(b+1,b+1+n)-b-1;
    for(int i = 1;i <= n;++i)
        a[i]=lower_bound(b+1,b+1+cnt,a[i])-b;
    for(int i = 1;i <= n;++i)buckt[a[i]]++;
    for(int i = 1;i <= n;++i)buckt[i]+=buckt[i-1];
    for(int i = 1;i <= n;++i)rk[i]=buckt[a[i]-1]+1;
    for(int t = 1;t <= n;t<<=1)
    {
        for(int i = 1;i <= n;++i)fir[i]=rk[i];
        for(int i = 1;i <= n;++i)sec[i]=(i+t>n?0:rk[i+t]);
        memset(buckt,0,sizeof(buckt));
        for(int i = 1;i <= n;++i)buckt[sec[i]]++;
        for(int i = 1;i <= n;++i)buckt[i]+=buckt[i-1];
        for(int i = 1;i <= n;++i)tmp[n-(--buckt[sec[i]])]=i;
        memset(buckt,0,sizeof(buckt));
        for(int i = 1;i <= n;++i)buckt[fir[i]]++;
        for(int i = 1;i <= n;++i)buckt[i]+=buckt[i-1];
        for(int j = 1,i;j <= n;++j)i = tmp[j],sa[buckt[fir[i]]--]=i;
        bool ok=true;
        for(int i = 1,j,last=0;i <= n;++i)
        {
            j = sa[i];
            if(!last)
            rk[j]=1;
            else if(fir[j]==fir[last]&&sec[j]==sec[last])
            rk[j]=rk[last],ok=false;
            else 
            rk[j]=rk[last]+1;
            last=j;
        }   
        if(ok)
        break;
    }
    for(int i = 1;i <= n;++i)rk[sa[i]]=i;
    for(int i = 1,k=0,j;i <= n;++i)
    {
        if(rk[i]==1)
        k=0;
        else
        {
            if(k)
            k--;
            j = sa[rk[i]-1];
            while(a[j+k]==a[i+k]&&j+k<=n&&i+k<=n)k++;
        }
        height[rk[i]]=k;
    }
}
int mn[20][maxn];
int Log[maxn];
int bin[20];
void ST(int n){
    bin[0]=1;
    for(int i=1;i<20;i++)
        bin[i]=bin[i-1]*2;//bin[i]表示2的i次方
    Log[0]=-1;
    for(int i=1;i<=100000;i++)
        Log[i]=Log[i/2]+1;//Log[i]表示log(i)
    for(int i=1;i<=n;i++)
        mn[0][i]=height[i];//显然i到i+2^0-1就i一个位置,那么最小值等于自己本身的值
    for(int i=1;i<=Log[n];i++)
        for(int j=1;j<=n;j++)
            if(j+bin[i]-1<=n)
                mn[i][j]=min(mn[i-1][j],mn[i-1][j+bin[i-1]]);//状态继承
}
int query(int x,int y)
{
    int t=Log[y-x+1];
    return min(mn[t][x],mn[t][y-bin[t]+1]);
}
int main()
{
    scanf("%s",s);
    int k;
    scanf("%d",&k);
    int n=strlen(s);
    SA(n);
    ST(n);
    int nowrk = 0;
    string ans;
    for(int i = 1;i <= n;++i)
    {
        int st = height[i]+1;
        for(int j = st;j <= n-sa[i]+1;++j)
        {
            int l = i+1,r = n;
            while(l<=r)
            {
                int mid=l+r>>1;
                if(query(i+1,mid)>=j)
                l = mid+1;
                else
                r = mid-1;
            }
            int tmp = l-1;
            // printf("%d\n",tmp);
            // system("pause");
            nowrk+=tmp-i+1;
            if(nowrk>=k)
            {
                for(int kk = sa[i],p = 0;p < j;++p)
                    ans+=s[kk+p-1];
                cout<<ans<<endl;
                return 0;
            }
        }
    }
    if(ans.size()>0)
    cout<<ans<<endl;
    else
    cout<<"No such line."<<endl;
    return 0;
}
posted @ 2019-11-12 21:40  tryatry  阅读(226)  评论(0编辑  收藏  举报