HDU 5030 Rabbit's String 后缀数组 二分 构造

题意:给出一个字符串,你最多将他分成K个子串,在每个子串中挑出字典序最大的子串,在从挑出的所有字符串中挑出字典序最大的字符串。现在希望,最后挑出的字符串足够小。

思路:最后挑出的字符串也是原字符串的子串。同时,字典序越大的字符串越有可能成为最后的结果。所以满足单调性,我们可以利用二分法求解。

          首先用后缀数组得到所有的不同子串。然后,直接二分答案,判断这个子串是否能够构造成满足题意的。

          如何构造呢?

          首先,要明白,我们的二分得到的答案是所有子串中最大的子串。其他的串的任何子串在字典序上是不能大于他的。

          这样,我们就可以去求出,对于以i为起始的子串,在哪里结束,能够使这个子串大于二分的答案。

          对于这个答案,在sa数组中,前面的所有的子串都比他小,那这个结束位置必须在整个串的结尾。

          在sa数组中,位于答案后面的字符串,如果两者没有公共前缀,那么不管怎么样分解,都可以找到一个比答案大的。这样就不符合上面的条件。

          但是有公共前缀的呢?这个时候,我们就要从前到后去构造了。如果这样的子串小于K个,那么我们可以通过合适的分割不让这些子串形成。如果这样的子串大于K个,那么无论怎样分割,都会出现至少一个这样的子串,这种情况下,这个答案就不对了。

注意:这个题目的关键是如果构造出这样的字符串。因为后缀数组是后缀的体现,我们可以从前往后递推,去构造这个字符串。

          同时,后缀数组对所有的子串进行了排序,对于某些满足单调性的判定问题,可以用二分法求解。

代码如下:

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

using namespace std;

const int MAX = 100010;

int K,n;
char str[MAX];
int r[MAX];
long long sum[MAX];
int limit[MAX];

    const int maxn =1000100;
    int rk[maxn],sa[maxn],height[maxn];
    int a[maxn],b[maxn],cnt[200000];
    //待排序字符串为r,长度为n,范围r[0] - r[n-1],值额范围0 - n-1.
    //字符集为m,范围1 - m;

    void radix_sort(int * r, int *a, int *b, int n, int m){//将a按照r进行基数排序,储存到b,长度为n,字符集为m
        memset(cnt,0,sizeof(cnt));
        for(int i = 0; i < n; ++i) ++cnt[r[a[i]]];
        for(int i = 1; i <= m; ++i) cnt[i] += cnt[i-1];
        for(int i = n -1; i >= 0; --i) b[--cnt[r[a[i]]]] = a[i];
    }

    void calc_sa(int*r, int n, int m){
        for(int i = 0; i < n; ++i) rk[i] =i;
        radix_sort(r,rk,sa,n,m);

        rk[sa[0]] = 0;
        for(int i = 1; i < n; ++i)
            rk[sa[i]]= rk[sa[i-1]] +(r[sa[i]]!=r[sa[i-1]]);
        for(int i = 0; 1<<i< n; ++i){
            for(int j = 0; j < n; ++j){
                a[j] = rk[j]+1;
                b[j] = j + (1<<i) >=n? 0: rk[j + (1<<i)] + 1;
                sa[j] = j;
            }
            radix_sort(b,sa,rk,n,n);
            radix_sort(a,rk,sa,n,n);
            rk[sa[0]] = 0;
            for(int j = 1; j < n; ++j){
                rk[sa[j]] = rk[sa[j-1]] + (a[sa[j-1]] != a[sa[j]] || b[sa[j-1]] != b[sa[j]]);
            }
        }
    }

    void calc_height(int * r,int n) {//计算height
        for(int i = 0 ; i < n; ++i) rk[sa[i]] = i;
        int h = 0;
        for(int i = 0; i < n; ++i){
            h = h == 0?0: h - 1;
            if(rk[i]!= 0)
                while(r[i + h] == r[sa[rk[i]-1] + h]) h++;
            height[rk[i]] = h;
        }
    }

bool judge(long long mid)
{
    int pos = lower_bound(sum + 1, sum + 1 + n,mid) - sum - 1;
    int len = mid - sum[pos] + height[pos];

    for(int i = 0 ; i < pos; ++i)
        limit[sa[i]] = n;
    limit[sa[pos]] = sa[pos] + len;
    int mii = height[pos+1];
    for(int i = pos + 1; i < n; ++i){
        mii = min(mii,height[i]);
        limit[sa[i]] = sa[i] + min(mii,len);
    }

    int need = 1;
    mii = n;
    for(int i = 0; i < n; ++i){
        if(i == limit[i]) return false;
        if(i >= mii){
            if(++need > K) return false;
            mii = n;
        }
        mii = min(mii,limit[i]);
    }
    return true;
}

int main(void)
{
    //freopen("input.txt","r",stdin);
    while(scanf("%d",&K), K){
        scanf("%s",str);
        n = 0;
        for(;str[n];n++)
            r[n] = str[n];
        calc_sa(r,n,256);
        calc_height(r,n);
        sum[0] = 0;
        for(int i = 1; i <= n; ++i)
            sum[i] = sum[i - 1] + n - sa[i-1] - height[i-1];
        long long lb = 0,ub = sum[n];
        while(lb + 1 < ub){
            long long  mid = (lb + ub) >> 1;
            if(judge(mid)) ub = mid;
            else lb = mid;
        }
        int pos = lower_bound(sum + 1, sum + 1 + n,ub) - sum - 1;
        int len = ub - sum[pos] + height[pos];
        pos = sa[pos];
        str[pos + len] = 0;
        printf("%s\n",str + pos);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值