20200129模拟赛T1 string

题目描述:

分析:

看到题目:后缀数组,二分,加加减减
然后。。。然后。。。

“后缀数组是个好东西,我有头发的时候天天写。”

我们首先要求出二分的区间,可能二分到所有子串字典序编号
二分的过程当中,首先先找出字典序当前值得字符串,这里要用到height数组RMQ
我们现在就需要将所有字典序大于该子串的切掉,从前往后枚举开头i,当LCP大于目标串的Len时,说明字典序更大,这时候要将i+Len位置切掉
最后通过切的次数来判断二分

以上说得很好。。。
真就写20分钟,调两个小时

“后缀数组是个好东西,我有头发的时候天天写。”

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>

#define maxn 200005

using namespace std;

inline int getint()
{
    int num=0,flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=num*10+c-48,c=getchar();
    return num*flag;
}

int n,k;
char s[maxn];
int st,len;
int sa[maxn],height[maxn];
int rk[maxn],bit[maxn],f[21][maxn];
int cnt[maxn],x[maxn],y[maxn];
int aa[maxn];

inline void suffix()
{
    memset(cnt,0,sizeof cnt);
    for(int i=1;i<=n;i++)cnt[int(s[i])]++;
    for(int i=1;i<=128;i++)cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;i--)sa[cnt[int(s[i])]--]=i;
    rk[sa[1]]=1;
    for(int i=2;i<=n;i++)rk[sa[i]]=rk[sa[i-1]]+(s[sa[i]]!=s[sa[i-1]]);
    for(int k=1;rk[sa[n]]!=n;k<<=1)
    {
        for(int i=1;i<=n;i++) 
        {
            x[i]=rk[i];
            y[i]=(i+k<=n)?rk[i+k]:0;
        }
        memset(cnt,0,sizeof cnt);
        for(int i=1;i<=n;i++)cnt[y[i]]++;
        for(int i=1;i<=n;i++)cnt[i]+=cnt[i-1];
        for(int i=n;i>=1;i--)rk[cnt[y[i]]--]=i;
        memset(cnt,0,sizeof cnt);
        for(int i=1;i<=n;i++)cnt[x[i]]++;
        for(int i=1;i<=n;i++)cnt[i]+=cnt[i-1];
        for(int i=n;i>=1;i--)sa[cnt[x[rk[i]]]--]=rk[i];
        rk[sa[1]]=1;
        for(int i=2;i<=n;i++)rk[sa[i]]=rk[sa[i-1]]+(x[sa[i]]!=x[sa[i-1]]||y[sa[i]]!=y[sa[i-1]]);
    }
    int now=0;
    for(int i=1;i<=n;i++)
    {
        if(now)now--;
        for(int j=sa[rk[i]+1];s[j+now]==s[i+now];now++);
        height[rk[i]]=now;
    }
    for(int i=1;i<=n;i++)
    {
        f[0][i]=height[i];
        bit[i]=bit[i-1];
        if(i>=(1<<(bit[i]+1)))bit[i]++;
    }
    for(int p=1;p<20;p++)
        for(int i=1,j=(1<<(p-1))+1;j<=n;i++,j++)
            f[p][i]=min(f[p-1][i],f[p-1][j]);
    for(int i=1;i<=n;i++)aa[i]=rk[i];
}

inline int LCP(int x,int y)
{
    if(x==y)return n-x+1;
    x=rk[x],y=rk[y];
    if(x>y)swap(x,y);
    int tmp=bit[y-x];
    return min(f[tmp][x],f[tmp][y-(1<<tmp)]);
}

inline long long getl()
{
    long long ans=1;
    for(int i=1;s[sa[i]]!=s[sa[n]];i++)
        ans+=n-sa[i]+1-height[i];
    return ans;
}

inline long long getr()
{
    long long ans=0;
    for(int i=1;i<=n;i++)
        ans+=n-sa[i]+1-height[i];
    return ans;
}

inline void solve(long long num)
{
    int pos;
    for(pos=1;n-sa[pos]+1<num;pos++)
        num-=n-sa[pos]+1-height[pos];
    st=sa[pos];len=num;
}

inline bool check(int k)
{
    if(len==1)return 0;
    int ans=0,ed=n;
    for(int i=1;i<=n;i++)
    {
        int tmp=LCP(i,st);
        if(tmp>=len)ed=min(ed,i+len-2);
        else if(s[i+tmp]>s[st+tmp])ed=min(ed,i+tmp-1);
        if(i==ed)ans++,ed=n;
    }
    return ans<=k;
}


int main()
{
    k=getint();
    scanf("%s",s+1);
    n=strlen(s+1);
    suffix();
    for(int i=1;i<=n;i++)rk[i]=aa[i];
    long long l=getl(),r=getr();
    while(l<r)
    {
        long long mid=(l+r+1)/2;
        solve(mid);
        if(check(k))r=mid-1;
        else l=mid;
    }
    solve(l);
    s[st+len]=0;
    printf("%s\n",s+st);
}

猜你喜欢

转载自www.cnblogs.com/Darknesses/p/12243581.html