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;
}


發佈了98 篇原創文章 · 獲贊 26 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章