后缀数组14题题解

后缀数组14题小结:

http://poj.org/problem?id=1226

题目大意:给出n个字符串,找出一个最长的字符串,使得它本身或它的反串在所有字符串中都出现过,多组数据。

分析:水题一道,把所有的串正反接一遍,中间用没出现过的字符隔开,然后跑一遍后缀数组。最后二分答案+height分块,记录每一块是否覆盖所有串即可,O(n*log(n))。

    其实KMP也可以做,枚举其中最短的那个串的子串,跟其他所有串跑一遍KMP即可。

CODE(suffix array):

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<stdio.h>
using namespace std;

const int maxl=21000;
const int maxn=110;
const int cur=400;

int s[maxl];
int len;

int cnt[maxl];
int temp1[maxl],temp2[maxl];
int *x,*y;

int sa[maxl];
int height[maxl];

int id[maxl];
bool vis[maxn];

int t,n;

int Comp(int *y,int a,int b,int l)
{
	return y[a]==y[b] && y[a+l]==y[b+l];
}

void Da()
{
	int m=1000;
	x=temp1,y=temp2;
	
	for (int i=0; i<m; i++) cnt[i]=0;
	for (int i=0; i<len; i++) cnt[ x[i]=s[i] ]++;
	for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
	for (int i=len-1; i>=0; i--) sa[ --cnt[ x[i] ] ]=i;
	
	int p=1;
	for (int j=1; p<len; j<<=1,m=p)
	{
		p=0;
		for (int i=len-j; i<len; i++) y[ p++ ]=i;
		for (int i=0; i<len; i++) if (sa[i]>=j) y[ p++ ]=sa[i]-j;
		
		for (int i=0; i<m; i++) cnt[i]=0;
		for (int i=0; i<len; i++) cnt[ x[ y[i] ] ]++;
		for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
		for (int i=len-1; i>=0; i--) sa[ --cnt[ x[ y[i] ] ] ]=y[i];
		
		swap(x,y);
		p=1;
		x[ sa[0] ]=0;
		for (int i=1; i<len; i++) x[ sa[i] ]=Comp(y,sa[i-1],sa[i],j)? p-1:p++;
	}
	
	int k=0;
	height[ x[0] ]=0;
	for (int i=0; i<len; i++)
	{
		if (k) k--;
		if (!x[i]) continue;
		int j=sa[ x[i]-1 ];
		while (s[i+k]==s[j+k]) k++;
		height[ x[i] ]=k;
	}
	
	/*for (int i=0; i<len; i++) printf("%d ",x[i]);
	printf("\n");
	for (int i=0; i<len; i++) printf("%d ",sa[i]);
	printf("\n");
	for (int i=0; i<len; i++) printf("%d ",height[i]);
	printf("\n");*/
}

bool Judge(int mid)
{
	for (int i=1; i<=n; i++) vis[i]=false;
	int num=0;
	int last=0;
	for (int i=0; i<len; i++)
	{
		if (height[i]<mid)
		{
			for (int j=last+1; j<=i; j++)
			{
				vis[ id[ sa[j] ] ]=false;
				int k=sa[j-1];
				if (k>=0) vis[ id[k] ]=false;
			}
			num=0;
			last=i;
		}
		else
		{
			int isi=id[ sa[i] ];
			if ( !vis[isi] && isi )
			{
				vis[isi]=true;
				num++;
			}
			int j=sa[i-1];
			if (j>=0)
			{
				if ( !vis[ id[j] ] && id[j] )
				{
					vis[ id[j] ]=true;
					num++;
				}
			}
			if (num>=n) return true;
		}
	}
	return false;
}

int Binary()
{
	int L=0,R=200;
	while (L+1<R)
	{
		int mid=(L+R)>>1;
		if ( Judge(mid) ) L=mid;
		else R=mid;
	}
	return L;
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	scanf("%d",&t);
	for (int q=1; q<=t; q++)
	{
		scanf("%d",&n);
		getchar();
		len=0;
		for (int i=1; i<=n; i++)
		{
			int L=len;
			char c=getchar();
			while ( c!='\n' )
			{
				s[ len ]=c;
				id[len]=i;
				s[ len++ ]+=cur;
				c=getchar();
			}
			int R=len-1;
			id[len]=0;
			s[ len++ ]=i+200;
			for (int j=R; j>=L; j--)
			{
				id[len]=i;
				s[ len++ ]=s[j];
			}
			id[len]=0;
			s[ len++ ]=n-i;
		}
		
		/*for (int i=0; i<len; i++) printf("%d ",s[i]);
		printf("\n");
		for (int i=0; i<len; i++) printf("%d ",id[i]);
		printf("\n");*/
		
		Da();
		
		int ans=Binary();
		printf("%d\n",ans);
	}
	
	return 0;
}

CODE(KMP):

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<stdio.h>
using namespace std;

const int maxn=110;

char s[maxn][maxn];
char r[maxn][maxn];
char temp[maxn];
int len[maxn];

char ans[maxn];
bool f[maxn];
int Next[maxn];
int t,n;
int Ans;

void Swap(char *p,char *q)
{
	for (int i=0; i<maxn; i++) temp[i]=p[i];
	for (int i=0; i<maxn; i++) p[i]=q[i];
	for (int i=0; i<maxn; i++) q[i]=temp[i];
}

void Preparation()
{
	int min_s=0;
	int min_len=10000001;
	for (int i=1; i<=n; i++)
		if (len[i]<min_len)
		{
			min_len=len[i];
			min_s=i;
		}
	Swap(s[1],s[min_s]);
	Swap(r[1],r[min_s]);
	swap(len[1],len[min_s]);
}

void Make_next(int L)
{
	Next[0]=Next[1]=0;
	int k=0;
	for (int i=2; i<=L; i++)
	{
		while ( k && ans[k]!=ans[i-1] ) k=Next[k];
		if (ans[k]==ans[i-1]) k++;
		Next[i]=k;
	}
}

void Work(char *q,int L,int ans_len,bool *F)
{
	int k=0;
	for (int i=0; i<L; i++)
	{
		while ( k && ans[k]!=q[i] ) k=Next[k];
		if ( ans[k]==q[i] ) k++;
		if (k==ans_len)
		{
			*F=true;
			break;
		}
	}
}

void Do(char *q)
{
	for (int L=len[1]; L>=1; L--)
		for (int i=0; i<=len[1]-L; i++)
		{
			for (int j=0; j<L; j++) ans[j]=q[i+j];
			Make_next(L);
			
			int j;
			for (j=2; j<=n; j++)
			{
				f[j]=false;
				Work(s[j],len[j],L,&f[j]);
				if (!f[j]) Work(r[j],len[j],L,&f[j]);
				if (!f[j]) break;
			}
			if (j!=n+1) continue;
			
			Ans=max(Ans,L);
			break;
		}
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	scanf("%d",&t);
	for (int q=1; q<=t; q++)
	{
		Ans=0;
		
		scanf("%d",&n);
		for (int i=1; i<=n; i++)
		{
			scanf("%s",&s[i]);
			len[i]=strlen(s[i]);
			for (int j=0; j<len[i]; j++) r[i][j]=s[i][len[i]-j-1];
		}
		
		Preparation();
		
		Do(s[1]);
		Do(r[1]);
		
		printf("%d\n",Ans);
	}
	
	return 0;
}

http://poj.org/problem?id=1743

题目大意:给出一个串,求其中最长的一个子串,使得它本身或它每一位+x在原串中出现了两次以上,并且不重叠。

分析:水题一道,求出每两位之间的差,跑后缀数组,二分答案+height分块,每一块看sa的最大值减最小值是否>=len即可。

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;

const int maxn=20110;

int a[maxn];

int cnt[maxn];
int temp1[maxn],temp2[maxn];
int *x=temp1,*y=temp2;

int sa[maxn];
int height[maxn];

int n;

int Comp(int *r,int a,int b,int l)
{
	return r[a]==r[b] && r[a+l]==r[b+l];
}

void Da()
{
	int m=200;
	
	for (int i=0; i<m; i++) cnt[i]=0;
	for (int i=0; i<n; i++) cnt[ x[i]=a[i] ]++;
	for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
	for (int i=n-1; i>=0; i--) sa[ --cnt[ x[i] ] ]=i;
	
	int p=1;
	for (int j=1; p<n; j<<=1,m=p)
	{
		p=0;
		for (int i=n-j; i<n; i++) y[ p++ ]=i;
		for (int i=0; i<n; i++) if (sa[i]>=j) y[ p++ ]=sa[i]-j;
		
		for (int i=0; i<m; i++) cnt[i]=0;
		for (int i=0; i<n; i++) cnt[ x[ y[i] ] ]++;
		for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
		for (int i=n-1; i>=0; i--) sa[ --cnt[ x[ y[i] ] ] ]=y[i];
		
		p=1;
		swap(x,y);
		x[ sa[0] ]=0;
		for (int i=1; i<n; i++) x[ sa[i] ]=Comp(y,sa[i-1],sa[i],j)? p-1:p++;
	}
}

void Calc_height()
{
	height[0]=0;
	int k=0;
	for (int i=0; i<n; i++)
	{
		if (k) k--;
		if (!x[i]) continue;
		int j=sa[ x[i]-1 ];
		while (a[i+k]==a[j+k]) k++;
		height[ x[i] ]=k;
	}
}

bool Judge(int z)
{
	int low,high;
	for (int i=0; i<n; i++)
	{
		if (height[i]<z)
		{
			low=20001;
			high=0;
		}
		else
		{
			low=min(low,sa[i]);
			high=max(high,sa[i]);
			low=min(low,sa[i-1]);
			high=max(high,sa[i-1]);
			if (low+z<high) return true;
		}
	}
	return false;
}

int Binary()
{
	int L=0,R=20001;
	while (L+1<R)
	{
		int mid=(L+R)>>1;
		if ( Judge(mid) ) L=mid;
		else R=mid;
	}
	return L;
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("my.out","w",stdout);
	
	while ( 1 )
	{
		scanf("%d",&n);
		if (!n) break;
		for (int i=0; i<n; i++) scanf("%d",&a[i]);
		for (int i=1; i<n; i++) a[i-1]=a[i]-a[i-1];
		for (int i=0; i<n; i++) a[i]+=100;
		a[n-1]=0;
		
		//for (int i=0; i<n; i++) printf("%d ",a[i]);
		//printf("\n");
		
		Da();
		
		Calc_height();
		
		int ans=Binary();
		if (ans>=4) printf("%d\n",ans+1);
		else printf("0\n");
		
		//for (int i=0; i<n; i++) printf("%d ",sa[i]);
		//printf("\n");
		//for (int i=0; i<n; i++) printf("%d ",height[i]);
		//printf("\n");
	}
	
	return 0;
}

http://poj.org/problem?id=2406

题目大意:给一个字符串,求一个最大的t,使得原串是某个串刚好复制t次得到的。

分析:先讲KMP的做法:若n-next[n]是n的因数则为n/(n-next[n]),否则为1。这个很好证明。

至于后缀数组,本质上也差不多,就是求一个最小的k使得lcp(suffix(0),suffix(k))==n-k。

CODE(KMP):

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=1000100;

int Next[maxn];
char s[maxn];
int len;

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	while ( scanf("%s",&s),s[0]!='.' )
	{
		len=strlen(s);
		int k=0;
		Next[0]=Next[1]=0;
		for (int i=2; i<=len; i++)
		{
			while ( k && s[k]!=s[i-1] ) k=Next[k];
			if ( s[k]==s[i-1] ) k++;
			Next[i]=k;
		}
		
		//for (int i=0; i<=len; i++) printf("%d ",Next[i]);
		//printf("\n");
		
		int ans=1;
		if ( len%(len-Next[len])==0 ) ans=len/(len-Next[len]);
		printf("%d\n",ans);
	}
	
	return 0;
}

CODE(suffix array)(我超时了,因为我不会DC3……):

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=1000100;

char s[maxn];
int a[maxn];

int cnt[maxn];
int temp1[maxn],temp2[maxn];
int *x=temp1,*y=temp2;

int sa[maxn];
int height[maxn];

bool f[maxn];
int num;

int Comp(int *r,int a,int b,int l)
{
	return r[a]==r[b] && r[a+l]==r[b+l];
}

void Da(int n)
{
	int m=30;
	
	for (int i=0; i<m; i++) cnt[i]=0;
	for (int i=0; i<n; i++) cnt[ x[i]=a[i] ]++;
	for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
	for (int i=n-1; i>=0; i--) sa[ --cnt[ x[i] ] ]=i;
	
	int p=1;
	for (int j=1; p<n; j<<=1,m=p)
	{
		p=0;
		for (int i=n-j; i<n; i++) y[ p++ ]=i;
		for (int i=0; i<n; i++) if (sa[i]>=j) y[ p++ ]=sa[i]-j;
		
		for (int i=0; i<m; i++) cnt[i]=0;
		for (int i=0; i<n; i++) cnt[ x[ y[i] ] ]++;
		for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
		for (int i=n-1; i>=0; i--) sa[ --cnt[ x[ y[i] ] ] ]=y[i];
		
		p=1;
		swap(x,y);
		x[ sa[0] ]=0;
		for (int i=1; i<n; i++) x[ sa[i] ]=Comp(y,sa[i-1],sa[i],j)? p-1:p++;
	}
}

void Calc_height(int n)
{
	height[0]=0;
	int k=0;
	for (int i=0; i<n; i++)
	{
		if (k) k--;
		if (!x[i]) continue;
		int j=sa[ x[i]-1 ];
		while (a[i+k]==a[j+k]) k++;
		height[ x[i] ]=k;
	}
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	while ( scanf("%s",&s) && s[0]!='.' )
	{
		int len=strlen(s);
		for (int i=0; i<len; i++) a[i]=s[i]-'a'+1;
		a[ len++ ]=0;
		
		Da(len);
		
		Calc_height(len);
		
		len--;
		int min_k=len,lcp=len;
		for (int i=x[0]+1; i<=len; i++)
		{
			lcp=min(lcp,height[i]);
			int k=sa[i];
			if ( lcp==len-k && len%k==0 ) min_k=min(min_k,k);
		}
		int ans=len/min_k;
		
		min_k=len,lcp=len;
		for (int i=x[0]; i>=0; i--)
		{
			lcp=min(lcp,height[i]);
			int k=sa[i-1];
			if ( lcp==len-k && len%k==0 ) min_k=min(min_k,k);
		}
		ans=max(ans,len/min_k);
		
		printf("%d\n",ans);
	}
	
	return 0;
}

http://poj.org/problem?id=2774

题目大意:两个串的最长公共子串。

分析:裸题一道……

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=200100;

char s[maxn];
int a[maxn];
int id[maxn];

int cnt[maxn];
int temp1[maxn],temp2[maxn];
int *x=temp1,*y=temp2;

int sa[maxn];
int height[maxn];

int Comp(int *r,int a,int b,int l)
{
	return r[a]==r[b] && r[a+l]==r[b+l];
}

void Da(int n,int m)
{
	for (int i=0; i<m; i++) cnt[i]=0;
	for (int i=0; i<n; i++) cnt[ x[i]=a[i] ]++;
	for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
	for (int i=n-1; i>=0; i--) sa[ --cnt[ x[i] ] ]=i;
	
	int p=1;
	for (int j=1; p<n; j<<=1,m=p)
	{
		p=0;
		for (int i=n-j; i<n; i++) y[ p++ ]=i;
		for (int i=0; i<n; i++) if (sa[i]>=j) y[ p++ ]=sa[i]-j;
		
		for (int i=0; i<m; i++) cnt[i]=0;
		for (int i=0; i<n; i++) cnt[ x[ y[i] ] ]++;
		for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
		for (int i=n-1; i>=0; i--) sa[ --cnt[ x[ y[i] ] ] ]=y[i];
		
		p=1;
		swap(x,y);
		x[ sa[0] ]=0;
		for (int i=1; i<n; i++) x[ sa[i] ]=Comp(y,sa[i-1],sa[i],j)? p-1:p++;
	}
}

void Calc_height(int n)
{
	height[0]=0;
	int k=0;
	for (int i=0; i<n; i++)
	{
		if (k) k--;
		if (!x[i]) continue;
		int j=sa[ x[i]-1 ];
		while (a[i+k]==a[j+k]) k++;
		height[ x[i] ]=k;
	}
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	scanf("%s",&s);
	int len1=strlen(s);
	scanf("%s",&s[len1]);
	int len2=strlen(s);
	for (int i=0; i<len1; i++) a[i]=s[i]-'a'+2;
	a[len1]=1;
	for (int i=len1; i<len2; i++) a[i+1]=s[i]-'a'+2;
	a[len2+1]=0;
	len2+=2;
	
	Da(len2,35);
	
	Calc_height(len2);
	
	for (int i=0; i<len1; i++) id[i]=1;
	id[len1]=4;
	for (int i=len1+1; i<len2-1; i++) id[i]=2;
	id[len2-1]=5;
	
	int ans=0;
	for (int i=1; i<len2; i++)
		if ( id[ sa[i-1] ]+id[ sa[i] ]==3 )
			ans=max(ans,height[i]);
	printf("%d\n",ans);
	
	return 0;
}

http://poj.org/problem?id=3261

题目大意:求一个最大的k,使得某个子串在原串中出现了k次以上,可重叠。

分析:二分答案,看height数组中是否有>=k的一段height都大于了当前答案。

但我们发现可以枚举任意一个长度为k的区间,用他的最小值去更新答案(很明显,长度更长一定不会优),这是滑动区间最小值问题,单调队列即可。

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=20100;
const int maxm=1000100;

int a[maxn];

int cnt[maxm];
int temp1[maxn],temp2[maxn];
int *x=temp1,*y=temp2;

int sa[maxn];
int height[maxn];

struct data
{
	int val,Time;
} que[maxn];
int head=1,tail=0;

int n,K;
int m=1000010;
int ans;

int Comp(int *r,int a,int b,int l)
{
	return r[a]==r[b] && r[a+l]==r[b+l];
}

void Da()
{
	for (int i=0; i<m; i++) cnt[i]=0;
	for (int i=0; i<n; i++) cnt[ x[i]=a[i] ]++;
	for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
	for (int i=n-1; i>=0; i--) sa[ --cnt[ x[i] ] ]=i;
	
	int p=1;
	for (int j=1; p<n; j<<=1,m=p)
	{
		p=0;
		for (int i=n-j; i<n; i++) y[ p++ ]=i;
		for (int i=0; i<n; i++) if (sa[i]>=j) y[ p++ ]=sa[i]-j;
		
		for (int i=0; i<m; i++) cnt[i]=0;
		for (int i=0; i<n; i++) cnt[ x[ y[i] ] ]++;
		for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
		for (int i=n-1; i>=0; i--) sa[ --cnt[ x[ y[i] ] ] ]=y[i];
		
		p=1;
		swap(x,y);
		x[ sa[0] ]=0;
		for (int i=1; i<n; i++) x[ sa[i] ]=Comp(y,sa[i-1],sa[i],j)? p-1:p++;
	}
}

void Calc_height()
{
	height[0]=0;
	int k=0;
	for (int i=0; i<n; i++)
	{
		if (k) k--;
		if (!x[i]) continue;
		int j=sa[ x[i]-1 ];
		while (a[i+k]==a[j+k]) k++;
		height[ x[i] ]=k;
	}
}

void Solve()
{
	for (int i=0; i<K; i++)
	{
		que[ ++tail ].val=height[i];
		que[tail].Time=i;
		while ( tail>1 && que[tail-1].val>que[tail].val )
		{
			tail--;
			que[tail]=que[tail+1];
		}
	}
	ans=que[head].val;
	for (int i=K; i<n; i++)
	{
		que[ ++tail ].val=height[i];
		que[tail].Time=i;
		while ( tail>head && que[tail-1].val>que[tail].val )
		{
			tail--;
			que[tail]=que[tail+1];
		}
		if (que[head].Time<=i-K) head++;
		ans=max(ans,que[head].val);
	}
}

void Print(int *r)
{
	for (int i=0; i<n; i++) printf("%d ",r[i]);
	printf("\n");
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	scanf("%d%d",&n,&K);
	
	if (K==1)
	{
		printf("%d\n",n);
		return 0;
	}
	
	for (int i=0; i<n; i++)
	{
		scanf("%d",&a[i]);
		a[i]++;
	}
	a[ n++ ]=0;
	
	Da();
	
	Calc_height();
	
	//Print(x);
	//Print(sa);
	//Print(height);
	
	K--;
	Solve();
	
	printf("%d\n",ans);
	return 0;
}

http://poj.org/problem?id=3294

题目大意:多个串的最长公共子串,并输出所有串。

分析:本题其实分成了两个任务:求长度+打印答案。前者用二分答案,后者单独做一遍即可。

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=101000;

int a[maxn];
char s[1010];

int cnt[maxn];
int temp1[maxn],temp2[maxn];
int *x=temp1,*y=temp2;

int sa[maxn];
int height[maxn];

int id[maxn];
bool f[maxn];
int N,n,m;

int Comp(int *r,int a,int b,int l)
{
	return r[a]==r[b] && r[a+l]==r[b+l];
}

void Da()
{
	m=500;
	
	for (int i=0; i<m; i++) cnt[i]=0;
	for (int i=0; i<n; i++) cnt[ x[i]=a[i] ]++;
	for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
	for (int i=n-1; i>=0; i--) sa[ --cnt[ x[i] ] ]=i;
	
	int p=1;
	for (int j=1; p<n; j<<=1,m=p)
	{
		p=0;
		for (int i=n-j; i<n; i++) y[ p++ ]=i;
		for (int i=0; i<n; i++) if (sa[i]>=j) y[ p++ ]=sa[i]-j;
		
		for (int i=0; i<m; i++) cnt[i]=0;
		for (int i=0; i<n; i++) cnt[ x[ y[i] ] ]++;
		for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
		for (int i=n-1; i>=0; i--) sa[ --cnt[ x[ y[i] ] ] ]=y[i];
		
		p=1;
		swap(x,y);
		x[ sa[0] ]=0;
		for (int i=1; i<n; i++) x[ sa[i] ]=Comp(y,sa[i-1],sa[i],j)? p-1:p++;
	}
}

void Calc_height()
{
	height[0]=0;
	int k=0;
	for (int i=0; i<n; i++)
	{
		if (k) k--;
		if (!x[i]) continue;
		int j=sa[ x[i]-1 ];
		while (a[i+k]==a[j+k]) k++;
		height[ x[i] ]=k;
	}
}

bool Judge(int mid)
{
	for (int i=0; i<=N; i++) f[i]=false;
	int num=0;
	int last=0;
	for (int i=1; i<n; i++)
		if (height[i]<mid)
		{
			for (int j=last+1; j<i; j++) f[ id[ sa[j] ] ]=f[ id[ sa[j-1] ] ]=false;
			last=i;
			num=0;
		}
		else
		{
			int isi=id[ sa[i] ];
			int isi1=id[ sa[i-1] ];
			if ( isi && !f[isi] )
			{
				f[isi]=true;
				num++;
			}
			if ( isi1 && !f[isi1] )
			{
				f[isi1]=true;
				num++;
			}
			if (2*num>N) return true;
		}
	return false;
}

int Binary()
{
	int L=0,R=1001;
	while (L+1<R)
	{
		int mid=(L+R)>>1;
		if ( Judge(mid) ) L=mid;
		else R=mid;
	}
	return L;
}

void Print(int len)
{
	for (int i=0; i<=N; i++) f[i]=false;
	int num=0;
	int last=0;
	height[n]=0;
	for (int i=1; i<=n; i++)
		if (height[i]<len)
		{
			if (2*num>N)
			{
				for (int j=0; j<len; j++) printf("%c",a[ sa[i-1]+j ]-200);
				printf("\n");
			}
			for (int j=last+1; j<i; j++) f[ id[ sa[j-1] ] ]=f[ id[ sa[j] ] ]=false;
			last=i;
			num=0;
		}
		else
		{
			int isi=id[ sa[i] ];
			int isi1=id[ sa[i-1] ];
			if ( isi && !f[isi] )
			{
				f[isi]=true;
				num++;
			}
			if ( isi1 && !f[isi1] )
			{
				f[isi1]=true;
				num++;
			}
		}
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	while ( scanf("%d",&N),N )
	{
		n=0;
		for (int i=1; i<=N; i++)
		{
			scanf("%s",&s);
			
			if (N==1)
			{
				printf("%s\n",s);
				continue;
			}
			
			int len=strlen(s);
			for (int j=0; j<len; j++)
			{
				a[n+j]=s[j];
				a[n+j]+=200;
				id[n+j]=i;
			}
			n+=len;
			a[n]=i;
			id[ n++ ]=0;
		}
		a[n-1]=0;
		
		//for (int i=0; i<n; i++) printf("%d %d\n",a[i],id[i]);
		//printf("\n");
		
		Da();
		
		Calc_height();
		
		int len=Binary();
		
		if (N!=1) if (len) Print(len);
		else printf("?\n");
		
		printf("\n");
	}
	
	return 0;
}

http://poj.org/problem?id=3415

题目大意:给出两个串A,B,求有多少个三元组(I,j,k)使得A从i开始的k位==B从j开始的k位,其中k要大于一个给定的数值x,多组数据。

分析:跑一遍后缀数组,然后我们在做到sa[i]的时候,我们要考虑它和sa[j](1<=j<I且sa[i],sa[j]来自不同的字符串)对答案的贡献。其中k可以取x~lcp(sa[i],sa[j])。

一开始我用了两棵线段树,3000ms,后来发现简直就是个傻叉。由于一个height[i]进栈会不断地把前面那些比它大的值压成和它自己一样的值,我们再分别记录一下当前栈中来自A,B串的总和是多少即可,900ms。

CODE(单调栈):

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=200100;
typedef long long LL;

char s[maxn];
int a[maxn];
int id[maxn];

int cnt[maxn];
int temp1[maxn],temp2[maxn];
int *x=temp1,*y=temp2;

int sa[maxn];
int height[maxn];

struct data
{
	LL val;
	int num[2];
} sak[maxn];
int tail;
LL sum[2];

int k,n;
LL ans;

int Comp(int *r,int a,int b,int l)
{
	return r[a]==r[b] && r[a+l]==r[b+l];
}

void Da(int m)
{
	for (int i=0; i<m; i++) cnt[i]=0;
	for (int i=0; i<n; i++) cnt[ x[i]=a[i] ]++;
	for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
	for (int i=n-1; i>=0; i--) sa[ --cnt[ x[i] ] ]=i;
	
	int p=1;
	for (int j=1; p<n; j<<=1,m=p)
	{
		p=0;
		for (int i=n-j; i<n; i++) y[ p++ ]=i;
		for (int i=0; i<n; i++) if (sa[i]>=j) y[ p++ ]=sa[i]-j;
		
		for (int i=0; i<m; i++) cnt[i]=0;
		for (int i=0; i<n; i++) cnt[ x[ y[i] ] ]++;
		for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
		for (int i=n-1; i>=0; i--) sa[ --cnt[ x[ y[i] ] ] ]=y[i];
		
		p=1;
		swap(x,y);
		x[ sa[0] ]=0;
		for (int i=1; i<n; i++) x[ sa[i] ]=Comp(y,sa[i-1],sa[i],j)? p-1:p++;
	}
}

void Calc_height()
{
	height[0]=0;
	int k=0;
	for (int i=0; i<n; i++)
	{
		if (k) k--;
		if (!x[i]) continue;
		int j=sa[ x[i]-1 ];
		while (a[i+k]==a[j+k]) k++;
		height[ x[i] ]=k;
	}
}

void Solve()
{
	ans=0;
	sum[0]=sum[1]=0;
	tail=0;
	for (int i=2; i<n; i++)
	{
		int v=height[i]-k+1;
		sak[ ++tail ].val=v;
		
		int isi=id[ sa[i-1] ];
		sum[isi]+=v;
		
		sak[tail].num[isi]=1;
		sak[tail].num[!isi]=0;
		
		while ( tail>1 && sak[tail-1].val>=sak[tail].val )
		{
			sum[0]-=sak[tail-1].num[0]*(sak[tail-1].val-v);
			sum[1]-=sak[tail-1].num[1]*(sak[tail-1].val-v);
			
			sak[tail].num[0]+=sak[tail-1].num[0];
			sak[tail].num[1]+=sak[tail-1].num[1];
			
			sak[--tail]=sak[tail+1];
		}
		
		if (v<=0)
		{
			sum[0]-=v*sak[tail].num[0];
			sum[1]-=v*sak[tail].num[1];
			tail--;
		}
		ans+=sum[ !id[ sa[i] ] ];
	}
	cout<<ans<<endl;
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	while ( scanf("%d",&k),k )
	{
		scanf("%s",&s);
		n=strlen(s);
		for (int i=0; i<n; i++)
		{
			a[i]=s[i];
			a[i]+=100;
			id[i]=0;
		}
		a[n]=499;
		id[ n++ ]=3;
		scanf("%s",&s);
		int len=strlen(s);
		for (int i=0; i<len; i++)
		{
			a[n+i]=s[i];
			a[n+i]+=100;
			id[n+i]=1;
		}
		n+=len;
		a[n]=0;
		id[ n++ ]=4;
		
		Da(500);
		
		Calc_height();
		
		Solve();
	}
	
	return 0;
}

CODE(线段树):

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=200100;
typedef long long LL;

char s[maxn];
int a[maxn];
int id[maxn];

int cnt[maxn];
int temp1[maxn],temp2[maxn];
int *x=temp1,*y=temp2;

int sa[maxn];
int height[maxn];

struct Tnode
{
	LL sum;
	int add;
	bool empty;
} tree[2][maxn<<2];

int k,n;
LL ans;

int Comp(int *r,int a,int b,int l)
{
	return r[a]==r[b] && r[a+l]==r[b+l];
}

void Da(int m)
{
	for (int i=0; i<m; i++) cnt[i]=0;
	for (int i=0; i<n; i++) cnt[ x[i]=a[i] ]++;
	for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
	for (int i=n-1; i>=0; i--) sa[ --cnt[ x[i] ] ]=i;
	
	int p=1;
	for (int j=1; p<n; j<<=1,m=p)
	{
		p=0;
		for (int i=n-j; i<n; i++) y[ p++ ]=i;
		for (int i=0; i<n; i++) if (sa[i]>=j) y[ p++ ]=sa[i]-j;
		
		for (int i=0; i<m; i++) cnt[i]=0;
		for (int i=0; i<n; i++) cnt[ x[ y[i] ] ]++;
		for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
		for (int i=n-1; i>=0; i--) sa[ --cnt[ x[ y[i] ] ] ]=y[i];
		
		p=1;
		swap(x,y);
		x[ sa[0] ]=0;
		for (int i=1; i<n; i++) x[ sa[i] ]=Comp(y,sa[i-1],sa[i],j)? p-1:p++;
	}
}

void Calc_height()
{
	height[0]=0;
	int k=0;
	for (int i=0; i<n; i++)
	{
		if (k) k--;
		if (!x[i]) continue;
		int j=sa[ x[i]-1 ];
		while (a[i+k]==a[j+k]) k++;
		height[ x[i] ]=k;
	}
}

void Build(int root,int L,int R)
{
	tree[0][root].sum=tree[0][root].add=0;
	tree[0][root].empty=false;
	tree[1][root].sum=tree[1][root].add=0;
	tree[1][root].empty=false;
	
	if (L==R) return;
	
	int left=root<<1;
	int right=left|1;
	int mid=(L+R)>>1;
	
	Build(left,L,mid);
	Build(right,mid+1,R);
}

void Down(int Id,int root,int L,int R,int left,int right,int mid)
{
	if (tree[Id][root].empty)
	{
		tree[Id][left].sum=tree[Id][left].add=0;
		tree[Id][right].sum=tree[Id][right].add=0;
		
		tree[Id][left].empty=tree[Id][right].empty=true;
		tree[Id][root].empty=false;
	}
	
	if (tree[Id][root].add)
	{
		tree[Id][left].add+=tree[Id][root].add;
		tree[Id][left].sum+=(long long)tree[Id][root].add*(mid-L+1);
		
		tree[Id][right].add+=tree[Id][root].add;
		tree[Id][right].sum+=(long long)tree[Id][root].add*(R-mid);
		
		tree[Id][root].add=0;
	}
}

void Update(int Id,int root,int L,int R,int x,int y)
{
	if ( y<L || R<x ) return;
	if ( x<=L && R<=y )
	{
		tree[Id][root].add++;
		tree[Id][root].sum+=(R-L+1);
		return;
	}
	
	int left=root<<1;
	int right=left|1;
	int mid=(L+R)>>1;
	
	Down(Id,root,L,R,left,right,mid);
	
	Update(Id,left,L,mid,x,y);
	Update(Id,right,mid+1,R,x,y);
	
	tree[Id][root].sum=tree[Id][left].sum+tree[Id][right].sum;
}

LL Query(int Id,int root,int L,int R,int x,int y)
{
	if ( y<L || R<x ) return 0;
	if ( x<=L && R<=y ) return tree[Id][root].sum;
	
	int left=root<<1;
	int right=left|1;
	int mid=(L+R)>>1;
	
	Down(Id,root,L,R,left,right,mid);
	
	LL vl=Query(Id,left,L,mid,x,y);
	LL vr=Query(Id,right,mid+1,R,x,y);
	
	return vl+vr;
}

void Clear(int root,int L,int R,int x,int y)
{
	if ( y<L || R<x ) return;
	if ( x<=L && R<=y )
	{
		tree[0][root].sum=tree[0][root].add=0;
		tree[0][root].empty=true;
		
		tree[1][root].sum=tree[1][root].add=0;
		tree[1][root].empty=true;
		
		return;
	}
	
	int left=root<<1;
	int right=left|1;
	int mid=(L+R)>>1;
	
	Down(0,root,L,R,left,right,mid);
	Down(1,root,L,R,left,right,mid);
	
	Clear(left,L,mid,x,y);
	Clear(right,mid+1,R,x,y);
	
	tree[0][root].sum=tree[0][left].sum+tree[0][right].sum;
	tree[1][root].sum=tree[1][left].sum+tree[1][right].sum;
}

void Solve()
{
	int N=k;
	for (int i=0; i<n; i++) N=max(N,height[i]+1);
	Build(1,1,N);
	LL ans=0;
	for (int i=1; i<n; i++)
	{
		Clear(1,1,N,height[i]+1,N);
		int isi=id[ sa[i] ];
		if (height[i]>=k) ans+=Query(!isi,1,1,N,k,height[i]);
		if ( isi==0 || isi==1 ) Update(isi,1,1,N,k,N);
		
		//for (int j=1; j<=N; j++) cout<<Query(1,1,1,N,j,j)<<' ';
		//printf("\n");
	}
	cout<<ans<<endl;
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	while ( scanf("%d",&k),k )
	{
		scanf("%s",&s);
		n=strlen(s);
		for (int i=0; i<n; i++)
		{
			a[i]=s[i];
			a[i]+=100;
			id[i]=0;
		}
		a[n]=50;
		id[ n++ ]=3;
		scanf("%s",&s);
		int len=strlen(s);
		for (int i=0; i<len; i++)
		{
			a[n+i]=s[i];
			a[n+i]+=100;
			id[n+i]=1;
		}
		n+=len;
		a[n]=0;
		id[ n++ ]=4;
		
		Da(500);
		
		Calc_height();
		
		Solve();
	}
	
	return 0;
}

http://poj.org/problem?id=3693

题目大意:给出一个串,求它的一个子串,使得这个子串是串t刚好重复k次拼成的,且k最大。如果有多个串输出字典序最小,多组数据。

分析:这题要输出字典序最小好恶心。我们要跑两个后缀数组:一个是正串的,一个是反串的。我们枚举t的长度L,然后检验s[i*L]与s[i*L+L]前后最长能匹配多少,假设最前能匹配到head,长度为len,我们的开头就可以是head~head+(len%L)这段,再用一个rmq求这段sa的最小值即可。In short:两个后缀数组,三个rmq。

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=100100;
const int maxl=20;

char s[maxn];
int a1[maxn];
int a2[maxn];

int cnt[maxn];
int temp1[maxn],temp2[maxn];
int *x=temp1,*y=temp2;

int Sa1[maxn],Sa2[maxn];
int Rank1[maxn],Rank2[maxn];
int Height1[maxn],Height2[maxn];
int rmq1[maxn][maxl],rmq2[maxn][maxl],rmq[maxn][maxl];

int P[maxn];
int n,Num=0;

void Make_P()
{
	P[1]=0;
	for (int i=2; i<maxn; i++) P[i]=P[i/2]+1;
}

int Comp(int *r,int a,int b,int l)
{
	return r[a]==r[b] && r[a+l]==r[b+l];
}

void Da(int *sa,int *rank,int *a,int *height,int m)
{
	for (int i=0; i<m; i++) cnt[i]=0;
	for (int i=0; i<n; i++) cnt[ x[i]=a[i] ]++;
	for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
	for (int i=n-1; i>=0; i--) sa[ --cnt[ x[i] ] ]=i;
	
	int p=1;
	for (int j=1; p<n; j<<=1,m=p)
	{
		p=0;
		for (int i=n-j; i<n; i++) y[ p++ ]=i;
		for (int i=0; i<n; i++) if (sa[i]>=j) y[ p++ ]=sa[i]-j;
		
		for (int i=0; i<m; i++) cnt[i]=0;
		for (int i=0; i<n; i++) cnt[ x[ y[i] ] ]++;
		for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
		for (int i=n-1; i>=0; i--) sa[ --cnt[ x[ y[i] ] ] ]=y[i];
		
		p=1;
		swap(x,y);
		x[ sa[0] ]=0;
		for (int i=1; i<n; i++) x[ sa[i] ]=Comp(y,sa[i-1],sa[i],j)? p-1:p++;
	}
	
	for (int i=0; i<n; i++) rank[ sa[i] ]=i;
	height[0]=0;
	int k=0;
	for (int i=0; i<n; i++)
	{
		if (k) k--;
		if (!rank[i]) continue;
		int j=sa[ rank[i]-1 ];
		while (a[i+k]==a[j+k]) k++;
		height[ rank[i] ]=k;
	}
}

void Make_rmq()
{
	for (int i=0; i<n; i++)
	{
		rmq1[i][0]=Height1[i];
		rmq2[i][0]=Height2[i];
		rmq[i][0]=i;
	}
	int maxj=P[n]+1;
	for (int j=1; j<maxj; j++)
		for (int i=0; i<n; i++)
		{
			int mid=i+(1<<(j-1));
			mid=min(mid,n-1);
			rmq1[i][j]=min(rmq1[i][j-1],rmq1[mid][j-1]);
			rmq2[i][j]=min(rmq2[i][j-1],rmq2[mid][j-1]);
			rmq[i][j]=rmq[i][j-1];
			if ( Rank1[ rmq[mid][j-1] ]<Rank1[ rmq[i][j] ] ) rmq[i][j]=rmq[mid][j-1];
		}
}

int Query1(int i,int j)
{
	int p=Rank1[i],q=Rank1[j];
	if (p>q) swap(p,q);
	p++;
	int lg=P[q-p+1];
	q=q-(1<<lg)+1;
	int len=min(rmq1[p][lg],rmq1[q][lg]);
	return len;
}

int Query2(int i,int j)
{
	int p=Rank2[i],q=Rank2[j];
	if (p>q) swap(p,q);
	p++;
	int lg=P[q-p+1];
	q=q-(1<<lg)+1;
	int len=min(rmq2[p][lg],rmq2[q][lg]);
	return len;
}

int Query(int p,int q)
{
	int lg=P[q-p+1];
	q=q-(1<<lg)+1;
	int h=rmq[p][lg];
	if ( Rank1[ rmq[q][lg] ]<Rank1[h] ) h=rmq[q][lg];
	return h;
}

void Solve()
{
	int ans=1,ansh=0,ansL=0;//ans==1 special judge
	for (int L=1; L<n-1; L++)
		for (int i=0; i<n; i+=L)
		{
			int j=i+L;
			if (j>=n-1) continue;
			int len1=Query1(i,j);
			int len2=Query2(n-i-1,n-j-1);
			int len=len1+len2;
			int h=i-len2;
			int na=len/L+1;
			int nh=Query(h,h+len%L);
			if ( na>ans || ( na==ans && Rank1[nh]<Rank1[ansh] ) )
			{
				ans=na;
				ansh=nh;
				ansL=L;
			}
		}
		
	if (ans>1)
		for (int i=1; i<=ans; i++)
			for (int j=0; j<ansL; j++)
				printf("%c",s[ansh+j]);
	else
	{
		int c='z';
		for (int i=0; i<n-1; i++) c=min(c,(int)s[i]);
		printf("%c",c);
	}
	printf("\n");
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	Make_P();
	
	while ( scanf("%s",&s),s[0]!='#' )
	{
		Num++;
		printf("Case %d: ",Num);
		
		n=strlen(s);
		for (int i=0; i<n; i++) a1[i]=s[i]-'a'+1;
		for (int i=0; i<n; i++) a2[i]=a1[n-i-1];
		a1[n]=0;
		a2[ n++ ]=0;
		
		Da(Sa1,Rank1,a1,Height1,27);
		Da(Sa2,Rank2,a2,Height2,27);
		
		Make_rmq();
		
		Solve();
	}
	
	return 0;
}

http://www.spoj.com/problems/PHRASES/

题目大意:给出n个串,求一个最长的串,使得它在每个串中不重叠地出现了至少2次:

分析:跟poj3294与poj1743差不多,水……

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=100100;
const int oo=100000001;

char s[10010];
int a[maxn];
int id[maxn];

int cnt[maxn];
int temp1[maxn],temp2[maxn];
int *x=temp1,*y=temp2;

int sa[maxn];
int height[maxn];

int low[maxn];
int high[maxn];

int t,N,n,m;

int Comp(int *r,int p,int q,int l)
{
	return r[p]==r[q] && r[p+l]==r[q+l];
}

void Da()
{
	for (int i=0; i<m; i++) cnt[i]=0;
	for (int i=0; i<n; i++) cnt[ x[i]=a[i] ]++;
	for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
	for (int i=n-1; i>=0; i--) sa[ --cnt[ x[i] ] ]=i;
	
	int p=1;
	for (int j=1; p<n; j<<=1,m=p)
	{
		p=0;
		for (int i=n-j; i<n; i++) y[ p++ ]=i;
		for (int i=0; i<n; i++) if (sa[i]>=j) y[ p++ ]=sa[i]-j;
		
		for (int i=0; i<m; i++) cnt[i]=0;
		for (int i=0; i<n; i++) cnt[ x[ y[i] ] ]++;
		for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
		for (int i=n-1; i>=0; i--) sa[ --cnt[ x[ y[i] ] ] ]=y[i];
		
		p=1;
		swap(x,y);
		x[ sa[0] ]=0;
		for (int i=1; i<n; i++) x[ sa[i] ]=Comp(y,sa[i-1],sa[i],j)? p-1:p++;
	}
}

void Calc_height()
{
	height[0]=0;
	int k=0;
	for (int i=0; i<n; i++)
	{
		if (k) k--;
		if (!x[i]) continue;
		int j=sa[ x[i]-1 ];
		while (a[i+k]==a[j+k]) k++;
		height[ x[i] ]=k;
	}
}

bool Judge(int mid)
{
	for (int i=1; i<=N; i++)
	{
		low[i]=oo;
		high[i]=-oo;
	}
	int last=0,num=0;
	for (int i=1; i<n; i++)
	{
		if (height[i]<mid)
		{
			for (int j=last+1; j<i; j++)
			{
				int temp=id[ sa[j-1] ];
				low[temp]=oo;
				high[temp]=-oo;
				temp=id[ sa[j] ];
				low[temp]=oo;
				high[temp]=-oo;
			}
			last=i;
			num=0;
		}
		else
		{
			int temp=id[ sa[i-1] ];
			if ( high[temp]-low[temp]<mid && temp )
			{
				high[temp]=max(high[temp],sa[i-1]);
				low[temp]=min(low[temp],sa[i-1]);
				if (high[temp]-low[temp]>=mid) num++;
			}
			temp=id[ sa[i] ];
			if ( high[temp]-low[temp]<mid && temp )
			{
				high[temp]=max(high[temp],sa[i]);
				low[temp]=min(low[temp],sa[i]);
				if (high[temp]-low[temp]>=mid) num++;
			}
			if (num>=N) return true;
		}
	}
	return false;
}

int Binary()
{
	int L=0,R=maxn;
	while (L+1<R)
	{
		int mid=(L+R)>>1;
		if ( Judge(mid) ) L=mid;
		else R=mid;
	}
	return L;
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("my.out","w",stdout);
	
	scanf("%d",&t);
	while (t--)
	{
		scanf("%d",&N);
		n=0;
		for (int i=1; i<=N; i++)
		{
			scanf("%s",&s);
			int len=strlen(s);
			while (s[len-1]==' ') len--;
			for (int j=0; j<len; j++)
			{
				a[n+j]=(int)s[j]+50;
				id[n+j]=i;
			}
			n+=len;
			a[n]=i;
			id[ n++ ]=i+50;
		}
		a[n-1]=0;
		m=500;
		
		Da();
		
		Calc_height();
		
		int ans=Binary();
		printf("%d\n",ans);
	}
	
	return 0;
}

http://www.spoj.com/problems/REPEATS/

题目大意:跟poj3693一样,不过输出k即可。

分析:这题是poj3693的简化版,不过我们可以考虑一些更简单的做法。我们求出s[i*L]与s[i*L+L]往后能匹配len个之后,我们可以认为它是在前面少了L-len%L个字符,然后我们把开头挪到那里去再搞一次尝试更新答案即可。

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=50100;
const int maxl=20;

int a[maxn];

int cnt[maxn];
int temp1[maxn],temp2[maxn];
int *x=temp1,*y=temp2;

int sa[maxn];
int height[maxn];

int lg[maxn];
int rmq[maxn][maxl];

int t,n,m;

void Make_lg()
{
	lg[1]=0;
	for (int i=2; i<maxn; i++) lg[i]=lg[i/2]+1;
}

int Comp(int *r,int p,int q,int l)
{
	return r[p]==r[q] && r[p+l]==r[q+l];
}

void Da()
{
	for (int i=0; i<m; i++) cnt[i]=0;
	for (int i=0; i<n; i++) cnt[ x[i]=a[i] ]++;
	for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
	for (int i=n-1; i>=0; i--) sa[ --cnt[ x[i] ] ]=i;
	
	int p=1;
	for (int j=1; p<n; j<<=1,m=p)
	{
		p=0;
		for (int i=n-j; i<n; i++) y[ p++ ]=i;
		for (int i=0; i<n; i++) if (sa[i]>=j) y[ p++ ]=sa[i]-j;
		
		for (int i=0; i<m; i++) cnt[i]=0;
		for (int i=0; i<n; i++) cnt[ x[ y[i] ] ]++;
		for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
		for (int i=n-1; i>=0; i--) sa[ --cnt[ x[ y[i] ] ] ]=y[i];
		
		p=1;
		swap(x,y);
		x[ sa[0] ]=0;
		for (int i=1; i<n; i++) x[ sa[i] ]=Comp(y,sa[i-1],sa[i],j)? p-1:p++;
	}
}

void Calc_height()
{
	height[0]=0;
	int k=0;
	for (int i=0; i<n; i++)
	{
		if (k) k--;
		if (!x[i]) continue;
		int j=sa[ x[i]-1 ];
		while (a[i+k]==a[j+k]) k++;
		height[ x[i] ]=k;
	}
}

void Make_rmq()
{
	for (int i=0; i<n; i++) rmq[i][0]=height[i];
	for (int j=1; j<=lg[n]; j++)
		for (int i=0; i<n; i++)
		{
			int mid=i+(1<<(j-1));
			mid=min(mid,n-1);
			rmq[i][j]=min(rmq[i][j-1],rmq[mid][j-1]);
		}
}

int Query(int i,int j)
{
	int p=x[i],q=x[j];
	if (p>q) swap(p,q);
	p++;
	int r=lg[q-p+1];
	q=q-(1<<r)+1;
	return min(rmq[p][r],rmq[q][r]);
}

void Solve()
{
	int ans=1;
	for (int L=1; L<n-1; L++)
		for (int i=0; i<n; i+=L)
		{
			int j=i+L;
			if (j>=n) break;
			int len=Query(i,j);
			ans=max(ans,len/L+1);
			int ni=i-(L-len%L);
			if (ni<0) continue;
			j=ni+L;
			len=Query(ni,j);
			ans=max(ans,len/L+1);
		}
	printf("%d\n",ans);
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	Make_lg();
	
	scanf("%d",&t);
	while (t--)
	{
		scanf("%d",&n);
		for (int i=0; i<n; i++)
		{
			char c=getchar();
			while ( c!='a' && c!='b' ) c=getchar();
			a[i]=c-'a'+1;
		}
		a[ n++ ]=0;
		m=3;
		
		Da();
		
		Calc_height();
		
		Make_rmq();
		
		Solve();
	}
	
	return 0;
}

http://www.spoj.com/problems/DISUBSTR/

题目大意:给出一个串,求有多少个不同的子串。

分析:水题,用len(sa[i])-height[i]更新答案即可(前提是len(sa[i])>height[i])。

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=1010;

char s[maxn];
int a[maxn];

int cnt[maxn];
int temp1[maxn],temp2[maxn];
int *x=temp1,*y=temp2;

int sa[maxn];
int height[maxn];

int id[maxn];
int N,n,m;

int Comp(int *r,int a,int b,int l)
{
	return r[a]==r[b] && r[a+l]==r[b+l];
}

void Da()
{
	for (int i=0; i<m; i++) cnt[i]=0;
	for (int i=0; i<n; i++) cnt[ x[i]=a[i] ]++;
	for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
	for (int i=n-1; i>=0; i--) sa[ --cnt[ x[i] ] ]=i;
	
	int p=1;
	for (int j=1; p<n; j<<=1,m=p)
	{
		p=0;
		for (int i=n-j; i<n; i++) y[ p++ ]=i;
		for (int i=0; i<n; i++) if (sa[i]>=j) y[ p++ ]=sa[i]-j;
		
		for (int i=0; i<m; i++) cnt[i]=0;
		for (int i=0; i<n; i++) cnt[ x[ y[i] ] ]++;
		for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
		for (int i=n-1; i>=0; i--) sa[ --cnt[ x[ y[i] ] ] ]=y[i];
		
		p=1;
		swap(x,y);
		x[ sa[0] ]=0;
		for (int i=1; i<n; i++) x[ sa[i] ]=Comp(y,sa[i-1],sa[i],j)? p-1:p++;
	}
}

void Calc_height()
{
	height[0]=0;
	int k=0;
	for (int i=0; i<n; i++)
	{
		if (k) k--;
		if (!x[i]) continue;
		int j=sa[ x[i]-1 ];
		while (a[i+k]==a[j+k]) k++;
		height[ x[i] ]=k;
	}
}

void Solve()
{
	for (int i=0; i<n; i++) id[i]=0;
	int ans=0,sum=0;
	for (int i=n-2; i>=0; i--)
	{
		sum++;
		sum-=id[i+1];
		ans+=sum;
		id[ height[ x[n-i-2] ] ]++;
	}
	printf("%d\n",ans);
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	scanf("%d",&N);
	while (N--)
	{
		scanf("%s",&s);
		n=strlen(s);
		for (int i=0; i<n; i++) a[i]=(int)s[i]+1;
		a[ n++ ]=0;
		m=500;
		
		Da();
		
		Calc_height();
		
		Solve();
	}
	
	return 0;
}

http://www.spoj.com/problems/SUBST1/

和上面那题一毛一样……

 

Timus Online Judge 1517:

题目大意:两个串的最长公共子串,并输出字典序最小。

分析:水……

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=200100;

char s[maxn];
int a[maxn];
int id[maxn];

int cnt[maxn];
int temp1[maxn],temp2[maxn];
int *x=temp1,*y=temp2;

int sa[maxn];
int height[maxn];

int Comp(int *r,int a,int b,int l)
{
	return r[a]==r[b] && r[a+l]==r[b+l];
}

void Da(int n,int m)
{
	for (int i=0; i<m; i++) cnt[i]=0;
	for (int i=0; i<n; i++) cnt[ x[i]=a[i] ]++;
	for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
	for (int i=n-1; i>=0; i--) sa[ --cnt[ x[i] ] ]=i;
	
	int p=1;
	for (int j=1; p<n; j<<=1,m=p)
	{
		p=0;
		for (int i=n-j; i<n; i++) y[ p++ ]=i;
		for (int i=0; i<n; i++) if (sa[i]>=j) y[ p++ ]=sa[i]-j;
		
		for (int i=0; i<m; i++) cnt[i]=0;
		for (int i=0; i<n; i++) cnt[ x[ y[i] ] ]++;
		for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
		for (int i=n-1; i>=0; i--) sa[ --cnt[ x[ y[i] ] ] ]=y[i];
		
		p=1;
		swap(x,y);
		x[ sa[0] ]=0;
		for (int i=1; i<n; i++) x[ sa[i] ]=Comp(y,sa[i-1],sa[i],j)? p-1:p++;
	}
}

void Calc_height(int n)
{
	height[0]=0;
	int k=0;
	for (int i=0; i<n; i++)
	{
		if (k) k--;
		if (!x[i]) continue;
		int j=sa[ x[i]-1 ];
		while (a[i+k]==a[j+k]) k++;
		height[ x[i] ]=k;
	}
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	int n;
	scanf("%d",&n);
	scanf("%s",&s);
	scanf("%s",&s[n]);
	for (int i=0; i<n; i++)
	{
		a[i]=(int)s[i]+1;
		id[i]=1;
	}
	for (int i=0; i<n; i++)
	{
		a[n+1+i]=(int)s[n+i]+1;
		id[n+1+i]=2;
	}
	a[n]=499;
	id[n]=3;
	n=n*2+2;
	a[n-1]=0;
	id[n-1]=4;
	
	Da(n,500);
	
	Calc_height(n);
	
	int ans=0,ansid=0;
	for (int i=1; i<n; i++)
		if ( id[ sa[i-1] ]+id[ sa[i] ]==3 && height[i]>=ans )
		{
			ans=height[i];
			ansid=sa[i];
		}
	
	for (int i=0; i<ans; i++) printf("%c",a[ansid+i]-1);
	printf("\n");
	
	return 0;
}

Timus Online Judge 1297:

题目大意:求原串的最长回文子串,相同长度则输出最前面一个。

分析:本人是用两次二分答案(由于单调性有点特殊),一次二分奇数长度,一次二分偶数。

其实可以正反串接起来做一遍后缀数组,然后枚举中间点。

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=2010;

char s[maxn];
int a[maxn];
int id[maxn];

int cnt[maxn];
int temp1[maxn],temp2[maxn];
int *x=temp1,*y=temp2;

int sa[maxn];
int height[maxn];

bool h[maxn];
bool t[maxn];

int n,m;

int Comp(int *r,int p,int q,int l)
{
	return r[p]==r[q] && r[p+l]==r[q+l];
}

void Da()
{
	for (int i=0; i<m; i++) cnt[i]=0;
	for (int i=0; i<n; i++) cnt[ x[i]=a[i] ]++;
	for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
	for (int i=n-1; i>=0; i--) sa[ --cnt[ x[i] ] ]=i;
	
	int p=1;
	for (int j=1; p<n; j<<=1,m=p)
	{
		p=0;
		for (int i=n-j; i<n; i++) y[ p++ ]=i;
		for (int i=0; i<n; i++) if (sa[i]>=j) y[ p++ ]=sa[i]-j;
		
		for (int i=0; i<m; i++) cnt[i]=0;
		for (int i=0; i<n; i++) cnt[ x[ y[i] ] ]++;
		for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
		for (int i=n-1; i>=0; i--) sa[ --cnt[ x[ y[i] ] ] ]=y[i];
		
		p=1;
		swap(x,y);
		x[ sa[0] ]=0;
		for (int i=1; i<n; i++) x[ sa[i] ]=Comp(y,sa[i-1],sa[i],j)? p-1:p++;
	}
}

void Calc_height()
{
	height[0]=0;
	int k=0;
	for (int i=0; i<n; i++)
	{
		if (k) k--;
		if (!x[i]) continue;
		int j=sa[ x[i]-1 ];
		while (a[i+k]==a[j+k]) k++;
		height[ x[i] ]=k;
	}
}

bool Judge(int len)
{
	for (int i=0; i<n; i++) h[i]=t[i]=false;
	int last=0;
	for (int i=1; i<n; i++)
	{
		if (height[i]<len)
		{
			for (int j=last+1; j<i; j++)
			{
				int nid=id[ sa[j-1] ];
				if (nid==1) h[ sa[j-1] ]=false;
				if (nid==2) t[ n-2-sa[j-1] ]=false;
				nid=id[ sa[j] ];
				if (nid==1) h[ sa[j] ]=false;
				if (nid==2) t[ n-2-sa[j] ]=false;
			}
			last=i;
		}
		else
		{
			int nid=id[ sa[i-1] ];
			if (nid==1)
			{
				h[ sa[i-1] ]=true;
				if (t[ sa[i-1]+len-1 ]) return true;
			}
			if (nid==2)
			{
				t[ n-2-sa[i-1] ]=true;
				if ( n-sa[i-1]-len-1>=0 &&  h[ n-sa[i-1]-len-1 ] ) return true;
			}
			
			nid=id[ sa[i] ];
			if (nid==1)
			{
				h[ sa[i] ]=true;
				if (t[ sa[i]+len-1 ]) return true;
			}
			if (nid==2)
			{
				t[ n-2-sa[i] ]=true;
				if ( n-sa[i]-len-1>=0 &&  h[ n-sa[i]-len-1 ] ) return true;
			}
		}
	}
	return false;
}

int Work(int len)
{
	for (int i=0; i<n; i++) h[i]=t[i]=false;
	int last=0,head=n;
	for (int i=1; i<n; i++)
	{
		if (height[i]<len)
		{
			for (int j=last+1; j<i; j++)
			{
				int nid=id[ sa[j-1] ];
				if (nid==1) h[ sa[j-1] ]=false;
				if (nid==2) t[ n-2-sa[j-1] ]=false;
				nid=id[ sa[j] ];
				if (nid==1) h[ sa[j] ]=false;
				if (nid==2) t[ n-2-sa[j] ]=false;
			}
			last=i;
		}
		else
		{
			int nid=id[ sa[i-1] ];
			if (nid==1)
			{
				h[ sa[i-1] ]=true;
				if (t[ sa[i-1]+len-1 ]) head=min(head,sa[i-1]);
			}
			if (nid==2)
			{
				t[ n-2-sa[i-1] ]=true;
				if ( n-sa[i-1]-len-1>=0 &&  h[ n-sa[i-1]-len-1 ] )
					head=min(head,n-sa[i-1]-len-1);
			}
			
			nid=id[ sa[i] ];
			if (nid==1)
			{
				h[ sa[i] ]=true;
				if (t[ sa[i]+len-1 ]) head=min(head,sa[i]);
			}
			if (nid==2)
			{
				t[ n-2-sa[i] ]=true;
				if ( n-sa[i]-len-1>=0 &&  h[ n-sa[i]-len-1 ] )
					head=min(head,n-sa[i]-len-1);
			}
		}
	}
	return head;
}

void Binary()
{
	int ans=1;
	int L=0,R=600;
	while (L+1<R)
	{
		int mid=(L+R)>>1;
		if ( Judge(mid*2) ) L=mid;
		else R=mid;
	}
	ans=max(ans,L*2);
	L=0,R=600;
	while (L+1<R)
	{
		int mid=(L+R)>>1;
		if ( Judge(mid*2+1) ) L=mid;
		else R=mid;
	}
	ans=max(ans,L*2+1);
	int head=Work(ans);
	for (int i=0; i<ans; i++) printf("%c",s[head+i]);
	printf("\n");
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	scanf("%s",&s);
	n=strlen(s);
	for (int i=0; i<n; i++)
	{
		a[i]=(int)s[i]+1;
		id[i]=1;
	}
	for (int i=n+1; i<=2*n; i++)
	{
		a[i]=a[2*n-i];
		id[i]=2;
	}
	a[n]=499;
	id[n]=3;
	n=n*2+2;
	a[n-1]=0;
	id[n-1]=4;
	m=500;
	
	Da();
	
	Calc_height();
	
	Binary();
	
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值