题意:将一个字符串分成不超过K段,使得这K段中,所有子串中字典序最大的最小。即每一段当中取一个最大的子串,再在所有段的最大子串再取一个最大值,让这个最大值最小。长度10W。
最大值最小,明显二分,但是乱二分是没前途的。要对所有本质不同的子串排名后二分,也就是你要能求出你二分出的第mid大的子串。
这个可以用后缀数组来搞,先求出sum(n-sa[i]-height[i])作为不同子串的总数,求第k大时扫描一下后缀数组,就能得到所有后缀从小到大的排名,如果当前后缀所能贡献的本质不同的子串尚不够k,将k减去这个后缀能贡献的子串数量然后继续扫描即可。(突然YY到当询问特别多的时候处理出n-sa[i]-height[i]的前缀和就可以通过二分来logn回答第k大子串23333).
然后因为用的是后缀数组,要从后往前贪心。定义一个函数来实现比较子串s(l1...r1)和s(l2...r2)的大小,可以用lcp来实现。然后不断地贪心,当字典序超过二分的值时,就把它划分开,然后看需要的划分次数是否超过K即可。
这个题的启发:按本质不同的子串的排名来二分。如何快速求出第k大的子串。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define rep(i,a,b) for(int i=a;i<=b;++i) #define erp(i,a,b) for(int i=a;i>=b;--i) #define LL long long using namespace std; const int MAXN = 100005; char s[MAXN]; int r[MAXN], N, ls, rs, K; int sa[MAXN], height[MAXN], rnk[MAXN]; LL tot; namespace SA { int wa[MAXN], wb[MAXN], wv[MAXN], ws[MAXN]; inline bool cmp(int*r, int a, int b, int l) { return r[a]==r[b] && r[a+l]==r[b+l]; } void da(int*r, int n, int m) { int i, j, p, *x = wa, *y = wb; for (i = 0; i<m; ++i) ws[i] = 0; for (i = 0; i<n; ++i) ws[x[i] = r[i]]++; for (i = 1; i<m; ++i) ws[i] += ws[i-1]; for (i = n-1; i>=0; --i) sa[--ws[x[i]]] = i; for (j = p = 1; p<n; j*=2, m = p) { for (p = 0, 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<n; ++i) wv[i] = x[y[i]]; for (i = 0; i<m; ++i) ws[i] = 0; for (i = 0; i<n; ++i) ws[wv[i]]++; for (i = 1; i<m; ++i) ws[i] += ws[i-1]; for (i = n-1; i>=0; --i) sa[--ws[wv[i]]] = y[i]; for (swap(x, y), p=1, x[sa[0]]=0, i=1; i<n; ++i) x[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? p-1 : p++; } } void get_height(int*r, int n) { rep(i, 1, n) rnk[sa[i]] = i; for (int i=0, k=0, j; i<n; height[rnk[i++]] = k) for (k?k--:0,j = sa[rnk[i]-1]; r[i+k] == r[j+k]; k++); } } int f[18][MAXN]; int lg2[MAXN]; void LogTable() { for (int i = 2, x = 0; i<MAXN; ++i) lg2[i] = (i != (i&-i)) ? x : ++x; } void MakeST() { rep(i, 1, N) f[0][i] = height[i]; rep(j, 1, 17) rep(i, 1, N) f[j][i] = min(f[j-1][i], f[j-1][i+(1<<(j-1))]); } inline int quary(int l, int r) { int t = lg2[r - l + 1]; return min(f[t][l], f[t][r - (1<<t) + 1]); } inline int lcp(int a, int b) { if (a == b) return N-a; a = rnk[a], b = rnk[b]; if (a > b) swap(a, b); return quary(a+1, b); } inline bool cmp(int l1, int r1, int l2, int r2) //str1 <= str2 { int len1 = r1-l1+1, len2 = r2-l2+1, co = lcp(l1, l2); if (len1<=len2 && co>=len1) return 1; if (len1>len2 && co>=len2) return 0; if (co>=len1 && co>=len2) return len1<=len2; return s[l1+co] <= s[l2+co]; } void getkth(LL k) { LL tot = 0, cur; rep(i, 1, N) tot += N-sa[i]-height[i]; rep(i, 1, N) { cur = N-sa[i]-height[i]; if (cur < k) { k -= cur; continue; } ls = sa[i], rs = ls+height[i]+k-1; break; } } bool check() { int cnt = 1, las = N-1; erp(i, N-1, 0) { if (s[i] > s[ls]) return 0; if (!cmp(i, las, ls, rs)) ++cnt, las = i; if (cnt > K) return 0; } return 1; } LL solve() { LL L = 1, R = tot, mid, ans; while (L <= R) { mid = (L+R)>>1; getkth(mid); if (check()) ans = mid, R = mid-1; else L = mid+1; } return ans; } int main() { cin >> K; scanf("%s", s); N = strlen(s); rep(i, 0, N-1) r[i] = s[i]-'a'+1; SA::da(r, N+1, 50); SA::get_height(r, N); LogTable(); MakeST(); lcp(21, 21); rep(i, 1, N) tot += N-sa[i]-height[i]; getkth(solve()); rep(i, ls, rs) putchar(s[i]); puts(""); return 0; }