[POI2000] 最长公共子串

  不知道为什么很多人学完SA后的第一道题都是这个。

  我寒假就写过这道题,而且SA只写了这道。

  但是,但是,但是,我今天又写了一遍,当然是A掉了。

  但是,我觉得手还比较生,准备在调整一下代码的格局。

  于是,我开始写第二遍。

  “好像不太对经啊。”

  “就是不对劲啊。”

  “怎么可能吗!”

  我对我的板子产生了质疑,于是来了组 aaaa 的数据测试一下。

  “。。。。。。”

  所以我是怎么A掉这题的......

  sa和rank都错了。

  不可思议。

  搞一下中间结果。

  “奥,我知道了。”

  在我原来的板子中,排名最小的后缀的排名是0,而对于长度不够没有第二关键字的后缀的第二关键字排名也是0。

  导致在第二次基数排序中(len=2),后缀Sufix(1)=aaa,后缀sufix(2)=aa的第一关键字与第二关键字均相同的情况。

  其中Sufix(1)的rank2由rank[1+2]=rank[3]=0得到,Sufix(2)的rank2由(i+l>=n)得到0,而显然这两个后缀是不同的的。

  在上午剩下的时间里我都在改板子了,最后算是改成正确并且满意的板子。

  细思极恐......

  回到这道题,对于SA的题,一般将最值问题转化为判定性问题。

  我们先将多个串接成一个串,中间用不同的字符连接,求一边SA。

  之后我们二分最大值x,并借助Height数组判定。

  首先,相同子串一定来自于一段连续的rank,也就是说要连续若干个Height值大于等于x。之后判断这连续若干个后缀中是否每个串的后缀都出现过。在拼串的时候记录一下每个位置的字符属于哪个串就可以了。

 

// q.c

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define mem(a) memset(a,0,sizeof(a))
using namespace std;
const int M=2000*5+100;
struct Suffix_Array {
	int n,m,h[M];
	int sa[M],tmp[M],cnt[M];
	int rank[M],rank1[M],rank2[M];
	Suffix_Array():n(0),m(0) {
		mem(h); mem(sa); mem(tmp); mem(cnt); mem(rank); mem(rank1); mem(rank2);
	}
	bool same(int x,int y) {
		return rank1[sa[x]]==rank1[sa[y]]&&rank2[sa[x]]==rank2[sa[y]]?true:false;
	}
	void get_SA(char *s) {
		mem(cnt);
		for(int i=1;i<=n;i++) cnt[s[i]-'a']++;
		for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
		for(int i=n;i>=1;i--) rank[i]=cnt[s[i]-'a'];
		for(int l=1;l<=n;l<<=1) {
			for(int i=1;i<=n;i++) {
				rank1[i]=rank[i];
				rank2[i]=(i+l<=n)?rank[i+l]:0;
			}
			mem(cnt);
			for(int i=1;i<=n;i++) cnt[rank2[i]]++;
			for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
			for(int i=n;i>=1;i--) tmp[cnt[rank2[i]]--]=i;
			mem(cnt);
			for(int i=1;i<=n;i++) cnt[rank1[i]]++;
			for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
			for(int i=n;i>=1;i--) sa[cnt[rank1[tmp[i]]]--]=tmp[i];
			rank[sa[1]]=1;
			for(int i=2;i<=n;i++) 
				rank[sa[i]]=same(i,i-1)?rank[sa[i-1]]:rank[sa[i-1]]+1;
			if(rank[sa[n]]==n) break; 
		}
	}
	void get_HT(char *s) {
		for(int i=1,j=0,L=0;i<=n;i++) if(rank[i]>1) {
			j=sa[rank[i]-1];
			while(i+L<=n&&j+L<=n&&s[i+L]==s[j+L]) ++L;
			h[rank[i]]=L; if(L) --L;
		}
	}
	void prepare(char *s,int x,int y,int a[],int b[]) {
		n=x; m=y;
		get_SA(s); get_HT(s);
		memcpy(a,sa,sizeof(sa));
		memcpy(b,h,sizeof(h));
	}
}SA;
char s[M]; int n,len,pos[M],H[M],Sa[M]; bool vis[M];
bool check(int x) {
	for(int i=1,j=0,sum=0;i<=len;i=j+1,sum=0) {
		memset(vis,0,sizeof(vis));
		for(j=i;j<=len&&H[j]>=x;j++) 
			if(pos[Sa[j-1]]&&pos[Sa[j]]&&pos[Sa[j-1]]!=pos[Sa[j]]) {
				if(!vis[pos[Sa[j-1]]]) vis[pos[Sa[j-1]]]=true,++sum;
				if(!vis[pos[Sa[j]]]) vis[pos[Sa[j]]]=true,++sum;
				if(sum>=n) return true;
			}
	}
	return false;
}
int main() {
	freopen("pow.in","r",stdin);
	freopen("pow.out","w",stdout);
	int l=1,mid=0,r=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {
		scanf("%s",s+l);
		r=strlen(s+1);
		for(int j=l;j<=r;j++) pos[j]=i;
		s[++r]='z'+i,l=++r;
	}
	SA.prepare(s,len=--r,30,Sa,H);
	for(l=0,mid=(l+r)>>1;l<=r;mid=(l+r)>>1) 
		check(mid)?l=mid+1:r=mid-1;
	printf("%d\n",l-1);
	return 0;
}

 

posted @ 2018-04-02 20:09  qjs12  阅读(141)  评论(0编辑  收藏  举报