AK-dream

【题目描述】
农夫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数组这里只给出求法 具体证明请自行百度

1
2
3
4
5
6
7
8
9
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的相同子串(自行理解一下)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#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;
}


 评论