人生第一道后缀数组题目,值得纪念下。
题目意思很简单,就是给予一个序列,求最长不相交的相同的连续子序列的长度(这里的相同,只要是两个序列的差值相等都算相同的序列)。
后缀数组模板题目:关键在于预处理上比较难想,(表示看了题解)。对于输入的序列,构造一个新的序列,该序列的每一位的值是原始序列相邻两位的差值。也就是说如果输入序列是a[i],那么新的序列new[i] = a[i+1] - a[i];对于new序列来说如果存在长度为n的不相交的连续相同子序列的话,那么a中一定存在n+1的满足题意的序列。然后套后缀数组的模板,求出height数组,然后二分枚举答案,对于每个答案,扫一遍height数组就可以得到答案了。
假设当前枚举的值是mid,那么我们对于height数组中如果存在一段连续值大于等于mid的,那么记录下这段连续值中sa[i]的最大值与最小值的,然后如果这两者的差大于等于mid则成立。这是由于height记录的相邻数组的最大公共前缀,本来就是针对那些已经按照字典序排列好的字符串,所以如果存在一段连续的height值大于mid那么这段的字符串的公共前缀一定大于等于k,所以就可以用maxsa - minsa,再判断是否相交就可以了。
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
#define Max_N (20000 + 100)
int n;
int k;
int a[Max_N];
int rank1[Max_N];
int tmp[Max_N];
bool compare_sa(int i, int j)
{
if(rank1[i] != rank1[j]) return rank1[i] < rank1[j];
else {
int ri = i + k <= n ? rank1[i + k] : -1;
int rj = j + k <= n ? rank1[j + k] : -1;
return ri < rj;
}
}
void construct_sa(int buf[], int s, int sa[])
{
int len = s;
for (int i = 0; i <= len; i++) {
sa[i] = i;
rank1[i] = i < len ? buf[i] : -1;
}
for ( k = 1; k <= len; k *= 2) {
sort(sa, sa + len +1, compare_sa);
tmp[sa[0]] = 0;
for (int i = 1; i <= len; i++) {
tmp[sa[i]] = tmp[sa[i-1]] + (compare_sa(sa[i-1], sa[i]) ? 1 : 0);
}
for (int i = 0; i <= len; i++) {
rank1[i] = tmp[i];
}
}
}
void construct_lcp(int buf[], int len, int *sa, int *lcp)
{
int h = 0;
lcp[0] = 0;
for (int i = 0; i < len; i++) {
int j = sa[rank1[i] - 1];
if (h > 0) h--;
for (; j + h < len && i + h < len; h++) {
if (buf[j+h] != buf[i+h]) break;
}
lcp[rank1[i] - 1] = h;
}
}
int sa[Max_N];
int rev[Max_N];
int a1[Max_N];
int lcp[Max_N];
int main()
{
while (true) {
scanf("%d", &n);
if (n == 0) return 0;
for (int i = 0; i < n; i++)
scanf("%d", &a[i]);
for (int i = 0; i < n - 1; i++)
a1[i] = a[i+1] - a[i] + 100;
n--;
construct_sa(a1, n, sa);
construct_lcp(a1, n, sa, lcp);
int l = 0; int r = n;
int ans = 0;
while (true) {
int mid = l + (r - l) / 2;
int flag = 0;
int min1 = 20000000;
int max1 = -1;
for (int i = n; i >= 0; i--){
//cout << lcp[sa[i]] << endl;
if (lcp[i] < mid) max1 = min1 = sa[i];
else {
min1 = min(min1, sa[i]);
max1 = max(max1, sa[i]);
if (max1 - min1 >= mid) {
flag = 1;
break;
}
}
}
if (flag) { l = mid + 1; ans = mid; }
else r = mid - 1;
if (l > r) break;
}
if (ans + 1 < 5) cout << "0" << endl;
else cout << ans+1 << endl;
}
}