题目
思路
首先有这样一个事实,如果两个字符串 a , b a,b a,b 满足 a < b a<b a<b ,那么二者的相同长度的前缀 a ′ , b ′ a',b' a′,b′ 满足 a ′ ≤ b ′ a'\le b' a′≤b′ 。这是显而易见的。同时,如果这个长度超过了二者的 l c p lcp lcp ,还会有 a ≤ b ′ a\le b' a≤b′ 。
另一个事实是前缀小于原串,也就是 a ′ ≤ a a'\le a a′≤a ,这也是毋庸置疑的。所以,如果长度超过 l c p lcp lcp ,我们有
a ′ ≤ a < b ′ ≤ b a'\le a<b'\le b a′≤a<b′≤b
那么我们先二分一个后缀,让大于这个后缀的都滚蛋。字典序比它小的后缀,肯定始终比它小;字典序比它大的,不能留下超过 l c p lcp lcp 的长度。所以我们会得到一些区间,限制条件是区间内至少切一刀。
怎么实现 “ 区间内至少切一刀 ” 的条件呢?这种题大家想必做的很多了,就是贪心,按照右端点排序,而后从左往右填。说白了就是,只有不得不切的时候才切。在本题中就比较容易实现。
所以我们找到一个字典序最小的后缀 a a a 使得最终答案不超过 a a a 。假设最终的答案是后缀 b ( b ≠ a ) b(b\ne a) b(b=a) 的一个前缀,那么 b > a b>a b>a ,否则 b b b 才是应当被找到的后缀。
如果这个前缀的长度超过 l c p ( a , b ) lcp(a,b) lcp(a,b) ,那么根据上面的结论有 a < b ′ a<b' a<b′ ,显然不成立(答案是不超过 a a a 的);所以其长度不超过 l c p ( a , b ) lcp(a,b) lcp(a,b) 。你惊讶地发现:它也是 a a a 的一个前缀。
所以答案一定是 a a a 的前缀。二分即可,越长的前缀字典序越大。还是利用上面的不等式进行判断。
复杂度 O ( n log n ) \mathcal O(n\log n) O(nlogn) ,用 S A \tt SA SA 即可。
代码
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long int_;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MaxN = 200010;
int sa[MaxN], rnk[MaxN<<1];
int tmp[MaxN<<1], buck[MaxN];
void prepare(int n,int m){
memset(buck,0,m<<2);
for(int i=0; i<n; ++i)
++ buck[rnk[i]];
for(int i=1; i<m; ++i)
buck[i] += buck[i-1];
}
# define XEZ(x) sa[-- buck[rnk[x]]] = x
/** @brief from index 0 to @p n-1 */
void getSA(int a[],int n){
memset(tmp+n,-1,n<<2); // EOF
memset(rnk+n,-1,n<<2); // EOF
memcpy(tmp,a,n<<2);
sort(tmp,tmp+n); // to hash
int m = unique(tmp,tmp+n)-tmp;
for(int i=0; i<n; ++i)
rnk[i] = lower_bound(tmp,
tmp+m,a[i])-tmp;
prepare(n,m);
for(int i=0; i<n; ++i) XEZ(i);
for(int w=1,p; m!=n; w<<=1,m=p+1){
for(int i=n-w+(p=0); i<n; ++i)
tmp[p ++] = i; // index
for(int i=0; i<n; ++i)
if(sa[i] >= w)
tmp[p ++] = sa[i]-w;
prepare(n,m);
for(int i=n-1; ~i; --i)
XEZ(tmp[i]); // big first
memcpy(tmp,rnk,n<<2);
rnk[sa[0]] = p = 0;
for(int i=1; i<n; ++i){
if(tmp[sa[i]] != tmp[sa[i-1]]||
tmp[sa[i]+w] != tmp[sa[i-1]+w])
++ p; // brand new one
rnk[sa[i]] = p;
}
}
}
int heit[MaxN]; // height
void getHei(int a[],int n){
for(int i=0,j,k=0; i<n; ++i)
if(rnk[i] == 0)
heit[0] = k = 0;
else{
if(k) -- k;
j = sa[rnk[i]-1];
while(max(i,j)+k <= n
&& a[j+k] == a[i+k])
++ k; // k = len
heit[rnk[i]] = k;
}
}
namespace RMQ{
int st[20][MaxN], *item;
int xez[MaxN]; // log2
void prepare(int a[],int n){
xez[0] = -1;
for(int i=0; i<n; ++i)
st[0][i] = i;
for(int i=1; i<=n; ++i)
xez[i] = xez[i>>1]+1;
item = a; // copy it
for(int j=0; (2<<j)<=n; ++j)
for(int i=0; i+(2<<j)<=n; ++i){
st[j+1][i] = st[j][i+(1<<j)];
if(a[st[j+1][i]] > a[st[j][i]])
st[j+1][i] = st[j][i];
}
}
int query(int l,int r){
int k = xez[r-l+1];
r = st[k][r-(1<<k)+1];
if(item[r] < item[st[k][l]])
return r; // minimum
return st[k][l];
}
}
char wyl[MaxN]; int xyx[MaxN];
int main(){
// freopen("4310\\2.in","r",stdin);
// freopen("xez.out","w",stdout);
int k = readint();
scanf("%s",wyl);
int n = strlen(wyl);
for(int i=0; i<n; ++i)
xyx[i] = wyl[i];
getSA(xyx,n), getHei(xyx,n);
RMQ::prepare(heit,n);
int L = 0, R = n-1; // ErFen
for(int mid=(L+R)>>1; L!=R; mid=(L+R)>>1){
int cnt = 0, now = n; // cut time
for(int i=0; i<n&&cnt<k; ++i){
if(rnk[i] > mid){
int x = RMQ::query(mid+1,rnk[i]);
if(heit[x] == 0) cnt = k;
now = min(now,i+heit[x]-1);
}
if(i == now) // it's a must
++ cnt, now = n;
}
if(now != n) ++ cnt;
if(cnt < k) // it's fine
R = mid; // it can be smaller
else L = mid+1; // not OK
}
int id = sa[L]; // which suf
// printf("id = %d, suf = ",id);
// puts(wyl+id);
L = heit[rnk[id]]+1, R = n-id; // length
// printf("BiSearch %d %d\n",L,R);
for(int mid=(L+R)>>1; L!=R; mid=(L+R)>>1){
int cnt = 0, now = n;
for(int i=0; i<n&&cnt<k; ++i){
if(rnk[i] > rnk[id]){
int x = RMQ::query(rnk[id]+1,rnk[i]);
x = min(heit[x],mid);
if(x == 0) cnt = k;
now = min(now,i+x-1);
}
else if(i == id)
now = min(now,i+mid-1);
if(i == now) ++ cnt, now = n;
}
if(now != n) ++ cnt;
if(cnt < k) R = mid;
else L = mid+1; // cannot
}
for(int i=id; i<id+L; ++i)
putchar(wyl[i]);
putchar('\n');
return 0;
}