题目: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; }
随后我会对后缀数组结合自己的做题还有国家集训队论文写个总结
poj 1226 hdu 1238 Substrings 求若干字符串正串及反串的最长公共子串 2002亚洲赛天津预选题,布布扣,bubuko.com
时间: 2024-12-24 12:43:48