题意:给出一个字符串,你最多将他分成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;
}