楼教主男人八题之poj1743

这题是学习罗的后缀数组中看到的,是我写的后缀数组的第三题,叫做后缀数组的简单应用,可怜我还是弄了半天才解决。

题目大意:寻找串中最长的不重叠的重复子串,给定的整数范围是1-88, 这个最长重复子串有可能是经过置换得到的,置换方法就是加上或减去一个整数k

解题思路:这里借用大牛的思路,只需要把串中相邻两个的差求出来组成一个串就能避免置换。

                  因为原串中经过置换之后能匹配的串,在他们的相邻差能组成的串中一定能匹配。使用差组成的串构建后缀数组,计算其height数组

                  使用二分法查找答案

ps:这题数据真水,后缀数组倍增算法请详见 罗穗骞《后缀数组——处理字符串的有力工具》


poj数据的数据太水了,以前的代码有错误都能过,现在更新了。

//Creat Time: 2013年05月26日 星期日 15时14分57秒
//File  Name: poj1743.cpp
//--Author--: GreedyDaam
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")//设置栈大小
using namespace std;
#define MAX 20005
int wa[MAX],wb[MAX],ws[MAX],wv[MAX];
int rank[MAX],height[MAX],str[MAX],sa[MAX];
bool 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 *sa,int n,int m){
	int i,j,p,*x=wa,*y=wb,*t;
	for(i=0;i<m;i++)ws[i]=0;
	for(i=0;i<n;i++)ws[x[i]=r[i]]++;
	for(i=1;i<m;i++)ws[i]+=ws[i-1];
	for(i=n-1;i>=0;i--)sa[--ws[x[i]]]=i;
	for(j=1,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++)ws[i]=0;
		for(i=0;i<n;i++)ws[wv[i]]++;
		for(i=1;i<m;i++)ws[i]+=ws[i-1];
		for(i=n-1;i>=0;i--)sa[--ws[wv[i]]]=y[i];
		for(t=x,x=y,y=t,x[sa[0]]=0,p=1,i=1;i<n;i++)
			x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
	}
}
void cal(int *r,int *sa,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++);
}
void binary(int *sa,int *h,int n){//二分法查找答案
	int i,mid=0,left,right,flag,low,high;//low标记以长度mid分组的sa数组中最小的后缀,high标记最大的
	left=0,right=n;                //left标记要寻找串长度的左边界,right右边界,flag标记有没有找到
	while(left<=right){
		mid=(right+left)/2;
		flag=0;low=high=sa[1];     //每次循环初始化flag,low,high
		for(i=2;i<=n&&!flag;i++){  //我们计算的height数组是从下标2开始的到n结束,
			if(h[i]<mid)low=high=sa[i]; //如果h[i]小于mid了,就是从这里开始分组
			else{
				low=min(low,sa[i]);            
				high=max(high,sa[i]);        
				if(high-low>mid)flag=1;//如果存在sa最大值和最小值之差大于mid就标记
			}
		}
		if(flag)left=mid+1;
		else right=mid-1;
	}
	if(right>=4)printf("%d\n",right+1);//这一切都是建立在相邻差构建的后缀数组的基础上,所以如果相邻差的最大不重叠子串是right那么真实的答案就应该是right+1
	else printf("0\n");//题目要求最小要5个才算
}
int main(){
     //freopen("input.txt","r",stdin);
	int i,t,k,n;
	while(scanf("%d",&n),n){
		scanf("%d",&t);
		for(i=0;i<n-1;i++){
			scanf("%d",&k);
			str[i]=k-t+88;
			t=k;
		}
		str[n-1]=0;
		if(n<10)printf("0\n");//如果n小于10就不用计算了
		else{
			da(str,sa,n,176);
			cal(str,sa,n-1);
			binary(sa,height,n-1);
		}
	}
return 0;
}





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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值