首页 > 代码库 > Hdu 5030 Rabbit's String (后缀数组)

Hdu 5030 Rabbit's String (后缀数组)

题目大意:

要求将一个长串分解成最多k个子串,使得分开的n个串的字典序最大的那一个子串的字典序最小。


思路分析:

要最大的最小,不难想到二分的。

我们二分出原串中的第rk大子串就是目标串。

现在就是怎么判断这个串满足要求,也就是我们如何分其他部分,使之成为字典序最大的一个。

我们可以通过rk轻易的找到这是哪一个串,假设它处在sa[t]中。

那么可以知道 在 sa数组中t以前的子串的字典序都是比目标串小的。

而后面会有比sa大的,我们就要分解这些串。

我们从t 扫描 到n的height  ,如果有一个height为0了,那么肯定是不行的,因为这个串的第一个都大。然后考虑不是0 的情况,那么就是说在这个子串的后面再加一个原串的下一个字母,就会比目标串大。所以这里就要切割,使之比目标串小。

然后最后判断你分割了多少个串,是否比k小。


#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define maxn 100005
using namespace std;
typedef long long ll;
int sa[maxn],t1[maxn],t2[maxn],c[maxn];
void suffix(const char *str,int n,int m)
{
    int *x=t1,*y=t2;
    for(int i=0; i<m; i++)c[i]=0;
    for(int i=0; i<n; i++)c[x[i]=str[i]]++;
    for(int i=1; i<m; i++)c[i]+=c[i-1];
    for(int i=n-1; i>=0; i--)sa[--c[x[i]]]=i;
    for(int k=1; k<=n; k<<=1)
    {
        int p=0;
        for(int i=n-k; i<n; i++)y[p++]=i;
        for(int i=0; i<n; i++)if(sa[i]>=k)y[p++]=sa[i]-k;
        for(int i=0; i<m; i++)c[i]=0;
        for(int i=0; i<n; i++)c[x[y[i]]]++;
        for(int i=0; i<m; i++)c[i]+=c[i-1];
        for(int i=n-1; i>=0; i--)sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(int i=1; i<n; i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
        if(p>=n)break;
        m=p;
    }
}
int rank[maxn],height[maxn];
void getheight(const char *str,int n)
{
    int k=0;
    for(int i=0; i<n; i++)rank[sa[i]]=i;
    for(int i=0; i<n; i++)
    {
        if(k)k--;
        if(!rank[i])continue;
        int j=sa[rank[i]-1];
        while(str[i+k]==str[j+k])k++;
        height[rank[i]]=k;
    }
}
char str[maxn];
ll sum[maxn];
int n,k;
int cc[maxn];

bool ok(ll rk)
{
    int t=lower_bound(sum+1,sum+n,rk)-sum;
    int l=sa[t];
    int r=sa[t]+rk-sum[t-1]+height[t]-1;
    int len=r-l+1;//找到目标串
    for(int i=0;i<n;i++)cc[i]=-1;//如果cc不等于-1,就代表在i切下了一个子串 [i,i+len].

    if(sa[t]+len<n-1)
    cc[sa[t]]=len;//如果这个串是不用切割的,也就是他的结尾就是原串的末尾

    for(int i=t+1;i<n;i++)
    {
        len=min(len,height[i]);
        if(len==0)return false;
        if(sa[i]+len<n-1)
        cc[sa[i]]=len;
    }

    int ans=0;

    r=n;

    for(int i=0;i<n;i++)
    {//在切割的时候有些问题,就是前面的时候已经切割到自己这一段区间,就可以跳过。
        if(i==r)
        {
            ans++;
            r=n;
            if(ans>=k)return false;
        }
        if(cc[i]!=-1)
        {
            r=min(r,i+cc[i]);
        }
    }
    return ans<k;//分解成了ans+1段
}
int main()
{
    while(scanf("%d",&k)!=EOF && k)
    {
        scanf("%s",str);
        n=strlen(str)+1;
        suffix(str,n,128);
        getheight(str,n);

        sum[0]=0;
        for(int i=1;i<n;i++)
            sum[i]=sum[i-1]+(n-sa[i]-height[i]-1);

        ll l=1,r=n*n,mid,ans=1;
        while(l<=r)
        {
            mid=(l+r)>>1;
            if(ok(mid))
            {
                ans=mid;
                r=mid-1;
            }
            else l=mid+1;
        }

        int t=lower_bound(sum+1,sum+n,ans)-sum;
        int ll=sa[t];
        int rr=sa[t]+ans-sum[t-1]+height[t]-1;

        for(int i=ll;i<=rr;i++)
            printf("%c",str[i]);
        puts("");
    }
    return 0;
}


Hdu 5030 Rabbit's String (后缀数组)