产奶模式「USACO 2006」

【题目描述】
农夫John发现他的奶牛产奶的质量一直在变动。经过细致的调查,他发现:虽然他不能预见明天产奶的质量,但连续的若干天的质量有很多重叠。我们称之为一个“模式”。 John的牛奶按质量可以被赋予一个\(0\)\(1000000\)之间的数。并且John记录了\(N(1\le N\le 20000)\)天的牛奶质量值。他想知道最长的出现了至少\(K(2\le K\le N)\)次的模式的长度。比如1 2 3 2 3 2 3 1 中 2 3 2 3出现了两次。当\(K=2\)时,这个长度为4。

【输入格式】
Line 1: 两个整数 N,K。
Lines 2..N+1: 每行一个整数表示当天的质量值。

【输出格式】
Line 1: 一个整数:N天中最长的出现了至少K次的模式的长度

题解

后缀数组+LCP(最长公共前缀)
这个组合的好处是什么呢 一个字符串中的每个子串都必然是一个后缀的前缀

定义suf(i)表示s[i~n]
定义height[i]表示suf(sa[i-1])与suf(sa[i])的最长公共前缀 即排名为i的后缀与他排名前一个的后缀的LCP
关于height数组这里只给出求法 具体证明请自行百度

void geth() {
	int p = 0;
	for (int i = 1; i <= n; i++) rnk[sa[i]] = i;
	for (int i = 1; i <= n; i++) {
		if (p) p--; int j = sa[rnk[i]-1];
		while (s[i+p] == s[j+p]) p++;
		height[rnk[i]] = p;
	}
}

引理1
\(LCP(suf(x), suf(y)) = min_{i=rnk[x]+1}^{rnk[y]} height[i]\)
证明略(其实是懒)

假设现在要找一个最长的子串 使得这个子串在原串中出现至少2次(可重叠)
只需要找出height数组的最大值即是答案
证明:假设\(LCP(suf(x), suf(y))=k\) 不妨设rnk[x]<rnk[y] 那么由引理1 一定有\(LCP(suf(x), suf(sa[rnk[x]+1]))=height[rnk[x]+1]\ge k\)
(sa[rnk[x]+1]即排名在suf(x)后一名的后缀)

那么如果出现至少3次呢 同理可得 如果存在一对连续的height[i],height[i+1]均大于等于m 就存在一个至少出现3次的长为m的子串

得出一般性结论 如果能找到一段连续k-1个height[i+1]~height[i+k-1]均大于等于m 那么就存在一个出现至少k次的长为m的子串
由引理1 这段区间内的k个后缀suf(sa[i])~suf(sa[i+k-1])必然是两两有长度至少为m的LCP

对于这题 可以二分答案mid 然后判断是否存在一段k-1个height全部大于等于mid
读入数据可以离散化一下 不离散化也无所谓 时间复杂度\(O(n\ log\ n)\)

扩展: 求一个最长子串使得这个子串在原串中出现至少2次(不可重叠)
方法: 二分答案 找出每一段 连续一些height全部大于二分值mid的区间 形如height[l+1]~height[r]
如果\(max_{i=l}^{r}sa[i]-min_{i=l}^{r}sa[i]\ge mid\) 则必定存在不重叠的长为mid的相同子串(自行理解一下)

代码

#include <bits/stdc++.h>
#define mx 1000005
using namespace std;

int s[mx], srt[mx], mxx;
int n, m, k, ans;
int sa[mx], sa2[mx], rnk[mx], key[mx], sum[mx], height[mx];

inline bool check(int *num, int a, int b, int l) { return num[a] == num[b] && num[a+l] == num[b+l]; }

inline void suffix() {
	int i, j, p; int *_rnk = rnk, *_sa2 = sa2, *tmp;
	for (i = 1; i <= m; i++) sum[i] = 0;
	for (i = 1; i <= n; i++) sum[_rnk[i]=s[i]]++;
	for (i = 2; i <= m; i++) sum[i] += sum[i-1];
	for (i = n; i >= 1; i--) sa[sum[_rnk[i]]--] = i;
	for (j = 1; p <= n; j <<= 1, m = p) {
		p = 0;
		for (i = n - j + 1; i <= n; i++) _sa2[++p] = i;
		for (i = 1; i <= n; i++) if (sa[i] > j) _sa2[++p] = sa[i] - j;
		for (i = 1; i <= n; i++) key[i] = _rnk[_sa2[i]];
		for (i = 1; i <= m; i++) sum[i] = 0;
		for (i = 1; i <= n; i++) sum[key[i]]++;
		for (i = 2; i <= m; i++) sum[i] += sum[i-1];
		for (i = n; i >= 1; i--) sa[sum[key[i]]--] = _sa2[i];
		for (tmp = _rnk, _rnk = _sa2, _sa2 = tmp, p = 2, _rnk[sa[1]] = 1, i = 2; i <= n; i++) {
			_rnk[sa[i]] = check(_sa2, sa[i-1], sa[i], j) ? p - 1 : p++;
		}
	}
} 

inline void geth() {
	int p = 0;
	for (int i = 1; i <= n; i++) rnk[sa[i]] = i;
	for (int i = 1; i <= n; i++) {
		if (p) p--; int j = sa[rnk[i]-1];
		while (s[i+p] == s[j+p]) p++;
		height[rnk[i]] = p;
	}
}

inline bool check(int mid) {
	int p = 0;
	for (int i = 2; i <= n; i++) {
		if (height[i] >= mid) p++;
		else p = 0;
		if (p >= k - 1) return 1;
	}
	return 0;
}

int main() {
	scanf("%d%d", &n, &k);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &s[i]); srt[i] = s[i];
	}
	sort(srt+1, srt+n+1);
	mxx = unique(srt+1, srt+n+1)-srt-1;
	for (int i = 1; i <= n; i++) {
		s[i] = lower_bound(srt+1, srt+mxx+1, s[i]) - srt;
	}
	m = 1000000;
	suffix(); geth();
	int l = 1, r = n, mid;
	while (l <= r) {
		mid = (l + r) >> 1;
		if (check(mid)) {
			ans = mid; l = mid + 1;
		} else r = mid - 1;
	}
	printf("%d\n", ans);
	return 0;
} 
posted @ 2020-01-20 22:31  AK_DREAM  阅读(221)  评论(0编辑  收藏  举报