后缀数组欧液!
大意就是给出一串数字 (1-88)
要寻找重复的最长不重叠公共子串儿 串长至少5
but 由题意 12345 和 23456 可以算是重复的公共子串 因为相当于每个数加上了相同的数
就是保持 差的顺序 不变啦
就转化成求 差的序列中 最长至少为4的不重叠公共子串儿
于是 字符串里放的是差值 但是由于前后差可能为负数 而每个字符串会作为基数排序中的下标 故要 加一个数使该数为正数
然后二份查找 下限可以从长度4开始
还要记录满足长度条件的间隔最大的下标 若〉4则满足条件
该题 trick多多
n==1的时候 输出为0
#include<iostream>
#include<string.h>
#include<cstdio>
#define MIN(a,b) (a)>(b)?(b):(a)
#define MAX(a,b) (a)>(b)?(a):(b)
#define INF 0x3f3f3f
using namespace std;
const int M=20010;
int N;
int str[M];
int sa[M] , rank[M] , height[M];
int wa[M] , wb[M] , wv[M] , wd[M];
int 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) //n为字符串长度,m为字符的取值范围,r为字符串。后面的j为每次排序时子串的长度
{
int i , j , p , *x = wa , *y = wb , *t;
for (i = 0 ; i < m ; i++)wd[i] = 0;
for (i = 0 ; i < n ; i++)wd[x[i]=r[i]]++;
for (i = 1 ; i < m ; i++)wd[i] += wd[i-1];
for (i = n-1 ; i >= 0 ; i--) sa[--wd[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++)wd[i] = 0;
for (i = 0 ; i < n ; i++)wd[wv[i]]++;
for (i = 1 ; i < m ; i++)wd[i] += wd[i-1];
for (i = n-1 ; i >= 0 ; i--)sa[--wd[wv[i]]] = y[i];
for (t = x , x = y , y = t , 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++;
}
}
/*height数组的值应该是从height[1]开始的,而且height[1]应该是等于0的。
原因是,+因为我们在字符串后面添加了一个0号字符(不好意思该题我没加),所以它必然是最小的
一个后缀。</span><span style="margin:0px; padding:0px; line-height:1.5"><span style="color:#ff0000">而字符串中的其他字符都应该是大于0的</span></span><span style="margin:0px; padding:0px; line-height:1.5"><span style="color:#ff0000">(前面有提到,使用倍
增算法前需要确保这点</span></span><span style="margin:0px; padding:0px; line-height:1.5; color:rgb(0,128,0)">),所以排名第二的字符串和0号字符的公共前缀
(即height[1])应当为0.在调用calheight函数时,要注意height数组的范
围应该是[1..n]。所以调用时应该是calheight(r,n) (所以我这题是 N-1)*/
void calheight(int *r , int n)
{
int i , j , k = 0;
for (i = 1 ; i <= n ; i++) rank[sa[i]] = i;
for (i = 0 ; i < n ; height[rank[i++]] = k)
for (k?k--:0,j = sa[rank[i]-1] ; r[i+k] == r[j+k] ; k++);
}
int find(int n)
{
int l=4,r=n;//长度至少为4 二分找最大长度
int ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
bool flag=0;
int num=0;
int head=INF,next=0;//标记满足条件的后缀的间隔最长前缀开头下标
for(int i=1;i<=n;i++){
if(height[i]>=mid)
{
num++;
head=MIN(head,MIN(sa[i-1],sa[i]));
next=MAX(next,MAX(sa[i-1],sa[i]));
}
else{
if(num+1>=2){
if(next-head>mid) //若满足间隔大于重复的长度 注意是大于不能等于 .等于的话可以参考输入 9个1时的例子
{
ans=mid;
flag=1;
}
}
num=0;
}
}
if(num+1>=2){
if(next-head>mid)
{
ans=mid;
flag=1;
}
}
if(flag)
l=mid+1;
else
r=mid-1;
}
return ans;//返回最长不重复差子串长度 若小于4即为0
}
int main()
{
//freopen("test.txt", "r", stdin);
while(scanf("%d",&N)!=EOF,N)
{
int i;
int a,b;
scanf("%d",&a);
if(N==1)
{
printf("0\n");
continue;
}
for(i=0;i<N-1;i++)
{
scanf("%d",&b);
str[i]=b-a+100;
a=b;
}
da(str,N,200);
calheight(str,N-1);
int tp=find(N-1);
if(tp)
printf("%d\n",tp+1);//因为是差值 所以要加上1才为真正的子串长
else
printf("0\n");
}
return 0;
}