BZOJ4310 跳蚤(后缀数组+二分答案)

题意:将一个字符串分成不超过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;
}


你可能感兴趣的:(BZOJ4310 跳蚤(后缀数组+二分答案))