题意
题解
分割的串长度至多为 ⌈ n / k ⌉ \lceil n / k\rceil ⌈n/k⌉,那么答案对应的分割方案中存在一个分割点位于 [ 0 , ⌈ n / k ⌉ ) [0, \lceil n / k\rceil) [0,⌈n/k⌉) 中,分割串数量为 O ( k ) O(k) O(k)。则对于固定的上界,只需要 O ( ⌈ n / k ⌉ × k ) O(\lceil n / k\rceil\times k) O(⌈n/k⌉×k) 次判断。
二分答案,转换为判定问题。将 s s s 复制一遍拼接在 s s s 后面以处理环形约束。二分 r a n k rank rank,即假设对应的后缀前 ⌈ n / k ⌉ \lceil n / k\rceil ⌈n/k⌉ 长度的前缀为答案(一定存在这样的后缀)。由于数位小的数字一定更小,那么可以在后缀 r a n k rank rank 小于二分值时贪心的取 ⌈ n / k ⌉ \lceil n / k\rceil ⌈n/k⌉ 长的分割段,若不满足则取 ⌈ n / k ⌉ − 1 \lceil n / k\rceil-1 ⌈n/k⌉−1。
#include <bits/stdc++.h>
using namespace std;
constexpr int MAXN = 4E5 + 5;
struct SA
{
int n, k;
int sa[MAXN], rnk[MAXN], tmp[MAXN];
void construct_sa(string s)
{
n = s.size();
for (int i = 0; i <= n; ++i)
sa[i] = i, rnk[i] = i < n ? s[i] : -1;
auto cmp = [&](int i, int j)
{
if (rnk[i] != rnk[j])
return rnk[i] < rnk[j];
int ri = i + k <= n ? rnk[i + k] : -1;
int rj = j + k <= n ? rnk[j + k] : -1;
return ri < rj;
};
for (k = 1; k <= n; k *= 2)
{
sort(sa, sa + n + 1, cmp);
tmp[sa[0]] = 0;
for (int i = 1; i <= n; ++i)
tmp[sa[i]] = tmp[sa[i - 1]] + (cmp(sa[i - 1], sa[i]) ? 1 : 0);
copy(tmp, tmp + n + 1, rnk);
}
}
} sa;
int N, K;
string S;
bool judge(int x)
{
int a = N / K + bool(N % K);
for (int i = 0; i < a; ++i)
{
int j = i;
for (int t = 0; t < K; ++t)
{
j += a - bool(x < sa.rnk[j]);
if (j - i >= N)
return 1;
}
}
return 0;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> N >> K >> S;
S += S;
sa.construct_sa(S);
int lb = 0, ub = N * 2;
while (ub - lb > 1)
{
int mid = (lb + ub) / 2;
if (judge(mid))
ub = mid;
else
lb = mid;
}
int len = N / K + bool(N % K);
for (int i = 0; i < len; ++i)
cout << S[sa.sa[ub] + i];
return 0;
}