poj 1743求最长不重叠公共子串


后缀数组欧液!


大意就是给出一串数字 (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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值