您的位置:首页 > 其它

hdoj 5030 后缀数组+二分

2015-08-05 19:37 489 查看
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');
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: