hdoj 5030 后缀数组+二分

来源:互联网 发布:爱思for mac官方下载 编辑:程序博客网 时间:2024/05/29 16:16

hdoj 5030

题意:给一个字符串,最多分割k次,求分割后最大的子串。

思路:

首先通过len - sa[i] - height[i]对子串去重(len是字符串长度),然后枚举最大的子串,看这个子串能否在分割k次以内实现。

第k个子串:

t = lower_bound(sum + 1, sum + 1 + len, k) - sum;// 即子串所属后缀的起始位置, sum[i]是到字符串第i位有多少不同的子串。
L = sa[t], R = len - (sum[t] - k + 1);

关键在于如何分割:因为每个子串都是某个后缀的前缀,所以要在sa[i]+length处分割(length是枚举的子串长度),做标记点(如果这个串是后缀就不用分割),然后遍历当前排名后面的后缀(这后面的后缀才有可能要被分割)做标记,最后在遍历标记找分割点,看是否满足条件,详见代码。

#include <cstdlib>#include <cstring>#include <cstdio>#include <algorithm>using namespace std;const int MAXN = 100010;int sa[MAXN];int t1[MAXN],t2[MAXN],c[MAXN];int Rank[MAXN],height[MAXN];void build_sa(int s[],int n,int m){    int i,j,p,*x=t1,*y=t2;    for(i=0;i<m;i++)c[i]=0;    for(i=0;i<n;i++)c[x[i]=s[i]]++;    for(i=1;i<m;i++)c[i]+=c[i-1];    for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;    for(j=1;j<=n;j<<=1)    {        p=0;        for(i=n-j;i<n;i++)y[p++]=i;        for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;        for(i=0;i<m;i++)c[i]=0;        for(i=0;i<n;i++)c[x[y[i]]]++;        for(i=1;i<m;i++)c[i]+=c[i-1];        for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];        swap(x,y);        p=1;x[sa[0]]=0;        for(i=1;i<n;i++)            x[sa[i]]=y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+j]==y[sa[i]+j]?p-1:p++;        if(p>=n)break;        m=p;    }}void getHeight(int s[],int n){    int i,j,k=0;    for(i=0;i<=n;i++)Rank[sa[i]]=i;    for(i=0;i<n;i++)    {        if(k)k--;        j=sa[Rank[i]-1];        while(s[i+k]==s[j+k])k++;        height[Rank[i]]=k;    }}int seq[MAXN];void suffix_array(char str[]){    int len = strlen(str);    for(int i = 0; i <= len; i++) seq[i] = str[i];    seq[len] = 0;    build_sa(seq, len + 1, 128);    getHeight(seq, len);}char str[MAXN];//==================================================================================long long sum[MAXN];int divide[MAXN], len;bool isOK(long long k, int n) {    int t = lower_bound(sum + 1, sum + 1 + len, k) - sum;    int l = sa[t], r = len - (sum[t] - k + 1);    int LEN = r - l + 1;    for(int i = 0; i <= len; i++) divide[i] = -1;    if(r + 1 < len) divide[sa[t]] = LEN;    for(int i = t + 1; i <= len; i++) {//做标记        LEN = min(LEN, height[i]);        if(height[i] == 0) return false;        if(sa[i] + LEN < len) divide[sa[i]] = LEN;    }    int cnt = 0;    r = len + 1;    for(int i = 0; i < len; i++) {//遍历标记找分割点        if(i == r) {            cnt++, r = len + 1;            if(cnt >= n) return false;        }        if(divide[i] != -1) r = min(r, i + divide[i]);    }    return cnt < n;}main() {    int n;    while(~scanf("%d", &n) && n) {        scanf("%s", str);        suffix_array(str);        len = strlen(str);        for(int i = 1; i <= len; i++) {            sum[i] = sum[i - 1] + len - sa[i] - height[i];        }        long long l = 1, r = sum[len], ans;        while(l <= r) {            long long mid = (l + r) / 2;            if(isOK(mid, n)) ans = mid, r = mid - 1;            else l = mid + 1;        }        int t = lower_bound(sum + 1, sum + 1 + len, ans) - sum;        l = sa[t], r = len - (sum[t] - ans + 1);        for(int i = l; i <= r; i++) putchar(str[i]);        putchar('\n');    }}



0 0
原创粉丝点击