题目:http://poj.org/problem?id=1226
http://acm.hdu.edu.cn/showproblem.php?pid=1238
其实用hash+lcp可能也可以,甚至可能写起来更快,不过我没试,我最近在练习后缀数组,所以来练手
后缀数组的典型用法之一----------------后缀数组+lcp+二分
思路:1、首先将所有的字符串每读取一个,就将其反转,作为一组,假设其下标为i到j,那么cnt[i]到cnt[j]都标记为一个数字(这个数字意思是第几个读入的字符串)
2、注意将字符串及其反串用一个未出现的字符隔开,我用的'\0'
3、二分答案,二分判断是不是合法的函数我折腾了很久...
lcp的时候 一个非常容易错的地方---不要把你加的空字符也算上
for(int i=0;i<n;i++)
{
int j=sa[rk[i]-1];
if(h>0)h--;
for(;j+h<n && i+h <n;h++)
if(s[j+h]!=s[i+h] || (s[j+h] == '\0' && s[i+h] == '\0'))break;/*****此处注意排除分隔符******/
lcp[rk[i]-1]=h;
}
最终是这么写的:
int C(int x)
{
memset(sea,0,sizeof(sea));
int num=0,ret=0,last=0;
for(int i=ns*2;i<n;i++)//<span style="font-family: Arial, Helvetica, sans-serif;">i=ns*2,因为开始ns*2-1个都是空字符</span>
{
if(lcp[i]>=x) //lcp的定义好好看看,里面的后缀本身就是按字典序的,所以<span style="font-family: Arial, Helvetica, sans-serif;">lcp[i]>=x能保证连着的这几个后缀的公共前缀相同</span>
{
sea[cnt[sa[i]]]=sea[cnt[sa[i+1]]]=1;
}
else<span style="white-space:pre"> </span>//看是不是合法,不合法就重新计数
{
for(int i=0;i<=ns;i++)
if(sea[i])ret++;
if(ret>=ns)
{
return 1;
}
else ret=0;
memset(sea,0,sizeof(sea));
}
}
for(int i=0;i<=ns;i++)
if(sea[i])ret++;
if(ret>=ns)return 1;
else return 0;
}
做这题花了很久,但是基本没参考题解,基本完全是自己做的,感觉不错
学到了:
1、读取s将s反转存到同一个数组里,写法还是蛮锻炼代码能力的
2、C的写法-------因为后缀数组+lcp+二分非常广泛,
#include <string>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <vector>
const int MAXN =20200;
using namespace std;
int n,k,alen,rkcnt,ns;//n=strlen(s);
int rk[MAXN],tmp[MAXN],sa[MAXN],lcp[MAXN],cnt[MAXN],sea[120];//rank为c++的保留字,所以rk代之,cnt记录术语第几个
char s[MAXN];
char str[101][101];
bool cmpSa(int i,int j)
{
if(rk[i] != rk[j])return rk[i]<rk[j];
else
{
int ri = i+k<=n?rk[i+k]:-1;
int rj = j+k<=n?rk[j+k]:-1;
return ri<rj;
}
}
void construct_sa()
{
//n=strlen(s);
for(int i=0;i<=n;i++)
{
sa[i]=i;
rk[i]=i<n?s[i]:-1;
}
for(k=1;k<=n;k*=2)
{
sort(sa,sa+n+1,cmpSa);
tmp[sa[0]]=0;
for(int i=1;i<=n;i++)
{
tmp[sa[i]]=tmp[sa[i-1]]+(cmpSa(sa[i-1],sa[i])?1:0);
}
for(int i=0;i<=n;i++)
rk[i]=tmp[i];
}
}
void construct_lcp()
{
for(int i=0;i<=n;i++)rk[sa[i]]=i;
int h=0;
lcp[0]=0;
for(int i=0;i<n;i++)
{
int j=sa[rk[i]-1];
if(h>0)h--;
for(;j+h<n && i+h <n;h++)
if(s[j+h]!=s[i+h] || (s[j+h] == '\0' && s[i+h] == '\0'))break;/*****此处注意排除分隔符******/
lcp[rk[i]-1]=h;
}
}
int C(int x)
{
memset(sea,0,sizeof(sea));
int num=0,ret=0,last=0;
for(int i=ns*2;i<n;i++)
{
if(lcp[i]>=x)
{
sea[cnt[sa[i]]]=sea[cnt[sa[i+1]]]=1;
}
else
{
for(int i=0;i<=ns;i++)
if(sea[i])ret++;
if(ret>=ns)
{
return 1;
}
else ret=0;
memset(sea,0,sizeof(sea));
}
}
for(int i=0;i<=ns;i++)
if(sea[i])ret++;
if(ret>=ns)return 1;
else return 0;
}
int main()
{
//freopen("hdu 1238.txt","r",stdin);
int ncase,len,ii,j,maxlen;
scanf("%d",&ncase);
while(ncase--)
{
alen=len=maxlen=rkcnt=0;
scanf("%d",&ns);
for(int i=0;i<ns;i++)
{
scanf("%s",s+alen);
len=strlen(s+alen);
maxlen=max(maxlen,len);
len++;
alen+=len;
for(j=alen,ii=alen-2;ii>=0 && s[ii];j++,ii--)
{
s[j]=s[ii];
}
s[j]=s[ii]='\0';
alen+=len;
}
alen--;
s[alen]='\0';
int flag=0;
for(int i=0;i<=alen;i++)
{
cnt[i]=rkcnt;
if(!s[i])
{
flag=!flag;
if(!flag)rkcnt++;
}
}
n=alen;
construct_sa();
construct_lcp();
int d=0,up=101,mid=101; /*二分上限的选取*/
while(up>1+d)
{
mid=(up+d)/2;
if(C(mid))d=mid;
else up=mid;
}
printf("%d\n",d);
}
return 0;
}
随后我会对后缀数组结合自己的做题还有国家集训队论文写个总结