首先:
jxrjxrjxr Orz,没有您我们都会死~
然后就是我从jxr神犇那里借鉴(照抄)过来的后缀数组模板。
#include<iostream>
#include<cstdio>
#include<cstring>
#define cmp(x) (y[sa[i]+x]==y[sa[i-1]+x])
using namespace std;
const int N=100000+1000;
char s[N];
int a[N],b[N],c[N],sa[N],rk[N],h[N],n,*x,*y;
void radix(int m){
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[y[i]]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i];
}
void getsa(int m){
for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i;
radix(m);
for(int k=1,p=0;k<=n;k<<=1,m=p,p=0){
for(int i=n-k+1;i<=n;i++)y[++p]=i;
for(int i=1;i<=n;i++)if(sa[i]>k)y[++p]=sa[i]-k;
radix(m);swap(x,y);x[sa[1]]=p=1;
for(int i=2;i<=n;i++)
x[sa[i]]=cmp(0)&&cmp(k)?p:++p;
if(p==n)return;
}
}
void geth(){
for(int i=1;i<=n;i++)rk[sa[i]]=i;
memset(h,0,sizeof(h));
for(int i=1,k=0;i<=n;h[rk[i++]]=k)
for(k?k--:1;i+k<=n&&s[i+k]==s[sa[rk[i]-1]+k];k++);
}
真是十分的 短 啊~~~~~~
话说我现在才会后缀数组真的好吗,简直违反基本法啊。
不过还是稀里糊涂地看懂(背下来)了。
于是就可以开心地去水题了。
没错其实我就是把09年那篇论文里面的题给水了下。
然后来写下一句话题解。
POJ1743:不可重叠最长重复子串
二分答案,height分组,维护最大最小sa,判定一下。
<span style="font-size:14px;">#include<iostream>
#include<cstdio>
#include<cstring>
#define cmp(x) (y[sa[i]+x]==y[sa[i-1]+x])
using namespace std;
const int N=20000+5;
int s[N],a[N],b[N],c[N],sa[N],rk[N],h[N],n,*x,*y;
int abs(int x){
return max(x,-x);
}
void radix(int m){
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[y[i]]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i];
}
void getsa(int m){
for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i;
radix(m);
for(int k=1,p=0;k<=n;k<<=1,m=p,p=0){
for(int i=n-k+1;i<=n;i++)y[++p]=i;
for(int i=1;i<=n;i++)if(sa[i]>k)y[++p]=sa[i]-k;
radix(m);swap(x,y);x[sa[1]]=p=1;
for(int i=2;i<=n;i++)
x[sa[i]]=cmp(0)&&cmp(k)?p:++p;
if(p==n)return ;
}
}
void geth(){
for(int i=1;i<=n;i++)rk[sa[i]]=i;
memset(h,0,sizeof(h));
for(int i=1,k=0;i<=n;h[rk[i++]]=k)
for(k?k--:1;i+k<=n&&s[i+k]==s[sa[rk[i]-1]+k];k++);
}
bool check(int k){
int _min=sa[1],_max=sa[1];
for(int i=2;i<=n+1;i++){
if(i==n+1||h[i]<k){
if(_max-_min>=k)return true;
_max=_min=sa[i];
}else{
_min=min(_min,sa[i]);
_max=max(_max,sa[i]);
}
}
return false;
}
int main(){
while(scanf("%d",&n)&&n){
for(int i=1;i<=n;i++)scanf("%d",&s[i]);
for(int i=1;i<n;i++)s[i]=s[i+1]-s[i]+100;
n--;
x=a;y=b;
getsa(200);geth();
int l=0,r=n/2;
while(l<=r){
int mid=l+r>>1;
if(check(mid))l=mid+1;
else r=mid-1;
}
if(l>=5)printf("%d\n",l);
else puts("0");
}
return 0;
}</span>
SPOJ 694&705:本质不同的子串个数
sigma(n-sa[i]+1-height[i])
<span style="font-size:14px;">#include<iostream>
#include<cstdio>
#include<cstring>
#define cmp(x) (y[sa[i]+x]==y[sa[i-1]+x])
using namespace std;
const int N=1000+5;
char s[N];
int sa[N],h[N],rk[N],a[N],b[N],c[N],*x,*y,n;
void radix(int m){
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[y[i]]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i];
}
void getsa(int m){
for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i;
radix(m);
for(int k=1,p=0;k<=n;k<<=1,m=p,p=0){
for(int i=n-k+1;i<=n;i++)y[++p]=i;
for(int i=1;i<=n;i++)if(sa[i]>k)y[++p]=sa[i]-k;
radix(m);swap(x,y);x[sa[1]]=p=1;
for(int i=2;i<=n;i++)
x[sa[i]]=cmp(0)&&cmp(k)?p:++p;
if(p==n)return;
}
}
void geth(){
for(int i=1;i<=n;i++)rk[sa[i]]=i;
memset(h,0,sizeof(h));
for(int i=1,k=0;i<=n;h[rk[i++]]=k)
for(k?k--:1;i+k<=n&&s[i+k]==s[sa[rk[i]-1]+k];k++);
}
int main(){
int T;scanf("%d",&T);
while(T--){
scanf("%s",s+1);
n=strlen(s+1);
x=a;y=b;
getsa(200);geth();
int ans=0;
for(int i=1;i<=n;i++)
ans+=n-sa[i]+1-h[i];
printf("%d\n",ans);
}
return 0;
}</span>
枚举长度l,计算次数,复杂度是n*调和级数(n),中间细节比较麻烦。。。。。
<span style="font-size:14px;">#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define cmp(x) (y[sa[i]+x]==y[sa[i-1]+x])
using namespace std;
const int N=100000+10;
struct Suffix_Array{
char s[N];
int a[N],b[N],c[N],sa[N],rk[N],h[N],n,*x,*y;
int st[N][20];
void init(char *t){
n=strlen(t+1);
for(int i=1;i<=n;i++)s[i]=t[i];
x=a;y=b;
}
void radix(int m){
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[y[i]]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i];
}
void getsa(int m){
for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i;
radix(m);
for(int k=1,p=0;k<=n;k<<=1,m=p,p=0){
for(int i=n-k+1;i<=n;i++)y[++p]=i;
for(int i=1;i<=n;i++)if(sa[i]>k)y[++p]=sa[i]-k;
radix(m);swap(x,y);x[sa[1]]=p=1;
for(int i=2;i<=n;i++)
x[sa[i]]=cmp(0)&&cmp(k)?p:++p;
if(p==n)return;
}
}
void geth(){
for(int i=1;i<=n;i++)rk[sa[i]]=i;
memset(h,0,sizeof(h));
for(int i=1,k=0;i<=n;h[rk[i++]]=k)
for(k?k--:1;i+k<=n&&s[i+k]==s[sa[rk[i]-1]+k];k++);
}
void rmq_init(){
for(int i=1;i<=n;i++)st[i][0]=h[i];
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
int rmq(int l,int r){
int k=log2(r-l+1);
return min(st[l][k],st[l+(1<<k)-1][k]);
}
int query(int i,int j){
int l=rk[i],r=rk[j];
if(l>r)swap(l,r);
return rmq(l+1,r);
}
void pre(){
getsa(200);geth();rmq_init();
}
}sol;
char s[N];
int a[N];
int main(){
int kase=0;
while(true){
scanf("%s",s+1);
if(s[1]=='#')break;
int n=strlen(s+1);
sol.init(s);sol.pre();
int ans=0,top=0;
for(int l=1;l<=n;l++)
for(int i=1;i+l<=n;i+=l){
int r=sol.query(i,i+l);
int ti=r/l+1;
int k=i-(l-r%l);
if(k>=1&&r%l)
if(sol.query(k,k+l)>=r)ti++;
if(ti>ans){
ans=ti;
a[top=1]=l;
}else if(ti==ans)a[++top]=l;
}
int len=-1,st;
for(int i=1;i<=n&&len==-1;i++)
for(int j=1;j<=top;j++){
int l=a[j];
if(sol.query(sol.sa[i],sol.sa[i]+l)>=(ans-1)*l){
len=l;
st=sol.sa[i];
break;
}
}
printf("Case %d: ",++kase);
for(int i=st,j=1;j<=len*ans;j++,i++)putchar(s[i]);
putchar('\n');
}
return 0;
}</span>
两个串中间加个奇怪的符号然后连接起来,跑一遍后缀数组,当sa[i]和sa[i-1]不在一个串时的最大height[i]即为答案。
<span style="font-size:14px;">#include<iostream>
#include<cstdio>
#include<cstring>
#define cmp(x) (y[sa[i]+x]==y[sa[i-1]+x])
using namespace std;
const int N=200000+10;
char s[N];
int a[N],b[N],c[N],sa[N],rk[N],h[N],n,*x,*y;
void radix(int m){
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[y[i]]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i];
}
void getsa(int m){
for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i;
radix(m);
for(int k=1,p=0;k<=n;k<<=1,m=p,p=0){
for(int i=n-k+1;i<=n;i++)y[++p]=i;
for(int i=1;i<=n;i++)if(sa[i]>k)y[++p]=sa[i]-k;
radix(m);swap(x,y);x[sa[1]]=p=1;
for(int i=2;i<=n;i++)
x[sa[i]]=cmp(0)&&cmp(k)?p:++p;
if(p==n)break;
}
}
void geth(){
for(int i=1;i<=n;i++)rk[sa[i]]=i;
memset(h,0,sizeof(h));
for(int i=1,k=0;i<=n;h[rk[i++]]=k)
for(k?k--:1;i+k<=n&&s[i+k]==s[sa[rk[i]-1]+k];k++);
}
int main(){
scanf("%s",s+1);
int m=strlen(s+1);
s[m+1]='$';
scanf("%s",s+m+2);
n=strlen(s+1);
x=a;y=b;
getsa(200);geth();
int ans=0;
for(int i=2;i<=n;i++)
if(sa[i-1]<=m^sa[i]<=m)ans=max(ans,h[i]);
printf("%d",ans);
return 0;
}</span>
POJ 3415:长度不小于k的公共子串个数
继续串接,跑一遍后缀数组,对于每一个A的后缀和B的后缀计算一次LCP算出答案,于是N^2爆炸,转用单调栈维护A的height,扫到B的时候算一下,反过来再做一次,得出总答案。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
#define cmp(x) (y[sa[i]+x]==y[sa[i-1]+x])
const int N=200000+5;
char s[N];
int a[N],b[N],c[N],sa[N],rk[N],h[N],n,*x,*y;
void radix(int m){
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[y[i]]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i];
}
void getsa(int m){
for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i;
radix(m);
for(int k=1,p=0;k<=n;k<<=1,m=p,p=0){
for(int i=n-k+1;i<=n;i++)y[++p]=i;
for(int i=1;i<=n;i++)if(sa[i]>k)y[++p]=sa[i]-k;
radix(m);swap(x,y);x[sa[1]]=p=1;
for(int i=2;i<=n;i++)
x[sa[i]]=cmp(0)&&cmp(k)?p:++p;
if(p==n)return;
}
}
void geth(){
for(int i=1;i<=n;i++)rk[sa[i]]=i;
memset(h,0,sizeof(h));
for(int i=1,k=0;i<=n;h[rk[i++]]=k)
for(k?k--:1;i+k<=n&&s[i+k]==s[sa[rk[i]-1]+k];k++);
}
int st[N][2];
int main(){
int k;
while(scanf("%d",&k)&&k){
scanf("%s",s+1);
int m=strlen(s+1);
s[++m]='$';
scanf("%s",s+m+1);
n=strlen(s+1);
x=a;y=b;
getsa(200);geth();
ll sum=0,tot=0,top=0;
for(int i=2;i<=n+1;i++){
if(h[i]<k)tot=top=0;
else{
int cnt=0;
if(sa[i-1]<m)cnt++,tot+=h[i]-k+1;
while(top&&h[i]<=st[top-1][0]){
top--;
tot-=st[top][1]*(st[top][0]-h[i]);
cnt+=st[top][1];
}
st[top][0]=h[i];st[top++][1]=cnt;
if(sa[i]>m)sum+=tot;
}
}
tot=top=0;
for(int i=2;i<=n+1;i++){
if(h[i]<k)tot=top=0;
else{
int cnt=0;
if(sa[i-1]>m)cnt++,tot+=h[i]-k+1;
while(top>0&&h[i]<=st[top-1][0]){
top--;
tot-=st[top][1]*(st[top][0]-h[i]);
cnt+=st[top][1];
}
st[top][0]=h[i];st[top++][1]=cnt;
if(sa[i]<m)sum+=tot;
}
}
printf("%lld\n",sum);
}
return 0;
}
POJ 3294:出现在不少于k个字符串中的最长子串
这题其实可以KMP做我会乱说= =+(常数我吃了)。
n个串连起来,中间有奇怪的不同的字符,跑一遍后缀数组,二分答案,height分组,每组判定一下有木有集齐(7个后缀召唤神龙?)k个不在同一个串的后缀,集齐了就可以(召唤神龙)return true
顺便说这题召唤出了我人生中的第一个OLE(我也不造怎么回事啊,然后莫名其妙就A了)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define cmp(x) (y[sa[i]+x]==y[sa[i-1]+x])
const int N=100000+1000;
char s[N];
int a[N],b[N],c[N],sa[N],rk[N],h[N],*x,*y,n;
void radix(int m){
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[y[i]]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i];
}
void getsa(int m){
for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i;
radix(m);
for(int k=1,p=0;k<=n;k<<=1,m=p,p=0){
for(int i=n-k+1;i<=n;i++)y[++p]=i;
for(int i=1;i<=n;i++)if(sa[i]>k)y[++p]=sa[i]-k;
radix(m);swap(x,y);x[sa[1]]=p=1;
for(int i=2;i<=n;i++)
x[sa[i]]=cmp(0)&&cmp(k)?p:++p;
if(p==n)return;
}
}
void geth(){
for(int i=1;i<=n;i++)rk[sa[i]]=i;
memset(h,0,sizeof(h));
for(int i=1,k=0;i<=n;h[rk[i++]]=k)
for(k?k--:1;i+k<=n&&s[i+k]==s[sa[rk[i]-1]+k];k++);
}
int in[N],T,k,now[105];
bool check(int len){
int tot=0;
T=1;
memset(now,0,sizeof(now));
for(int i=1;i<=n;i++){
if(h[i]<len){
if(tot>k)return true;
tot=1;
now[in[sa[i]]]=++T;
}
else{
if(now[in[sa[i]]]!=T){
now[in[sa[i]]]=T;
tot++;
}
}
}
if(tot>k)return true;
return false;
}
int ans[N];
void print(int len){
int tot=0,sz=0;
T=1;
memset(now,0,sizeof(now));
for(int i=1;i<=n;i++){
if(h[i]<len){
if(tot>k)ans[++sz]=sa[i-1];
tot=1;
now[in[sa[i]]]=++T;
}
else{
if(now[in[sa[i]]]!=T){
now[in[sa[i]]]=T;
tot++;
}
}
}
if(tot>k)ans[++sz]=sa[n];
ans[0]=sz;
}
int main(){
int m;bool flag=false;
while(scanf("%d",&m)&&m){
memset(s,0,sizeof(s));
int tmp=1,top=1;
int l=0,r=1005;
for(int i=1;i<=m;i++){
scanf("%s",s+tmp);
int len=strlen(s+tmp);
r=min(r,len);
for(int j=tmp;j<tmp+len;j++)
in[j]=i;
s[tmp+=len]=top++;
tmp++;
}
n=strlen(s+1);
x=a;y=b;
getsa(300);geth();
k=m/2;
while(l<=r){
int mid=l+r>>1;
if(check(mid))l=mid+1;
else r=mid-1;
}
if(flag)puts("");
flag=true;
if(l==1)puts("?");
else{
print(l-1);
for(int i=1;i<=ans[0];i++){
for(int j=ans[i];j<ans[i]+l-1;j++)
putchar(s[j]);
putchar('\n');
}
}
}
return 0;
}
SPOJ 220:每个字符至少出现两次且不重叠的最长子串
其实。。。。。这题跟上题差不多,那个至少出现两次并无卵用,因为判断的时候只要串长不为0,出现1次的都干掉了。
串接,二分答案,height分组(TM还能有点别的?)然后维护mx和mn,即最大最小sa值(跟第1题的不可重叠一样的)。
#include<iostream>
#include<cstdio>
#include<cstring>
#define cmp(x) (y[sa[i]+x]==y[sa[i-1]+x])
using namespace std;
const int N=100000+1000;
char s[N];
int a[N],b[N],c[N],sa[N],rk[N],h[N],n,*x,*y;
void radix(int m){
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[y[i]]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i];
}
void getsa(int m){
for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i;
radix(m);
for(int k=1,p=0;k<=n;k<<=1,m=p,p=0){
for(int i=n-k+1;i<=n;i++)y[++p]=i;
for(int i=1;i<=n;i++)if(sa[i]>k)y[++p]=sa[i]-k;
radix(m);swap(x,y);x[sa[1]]=p=1;
for(int i=2;i<=n;i++)
x[sa[i]]=cmp(0)&&cmp(k)?p:++p;
if(p==n)return;
}
}
void geth(){
for(int i=1;i<=n;i++)rk[sa[i]]=i;
memset(h,0,sizeof(h));
for(int i=1,k=0;i<=n;h[rk[i++]]=k)
for(k?k--:1;i+k<=n&&s[i+k]==s[sa[rk[i]-1]+k];k++);
}
int in[N],mx[15],mn[15],m;
bool check(int len){
memset(mx,0,sizeof(mx));
memset(mn,0x3f,sizeof(mn));
for(int i=2;i<=n+1;i++){
if(h[i]<len){
memset(mx,0,sizeof(mx));
memset(mn,0x3f,sizeof(mn));
mx[in[sa[i]]]=sa[i];
mn[in[sa[i]]]=sa[i];
}else{
mx[in[sa[i]]]=max(mx[in[sa[i]]],sa[i]);
mn[in[sa[i]]]=min(mn[in[sa[i]]],sa[i]);
mx[in[sa[i-1]]]=max(mx[in[sa[i-1]]],sa[i-1]);
mn[in[sa[i-1]]]=min(mn[in[sa[i-1]]],sa[i-1]);
int j;
for(j=1;j<=m;j++)
if(mx[j]-mn[j]<len)break;
if(j>m)return true;
}
}
return false;
}
int main(){
int T;scanf("%d",&T);
while(T--){
scanf("%d",&m);
n=1;
int l=0,r=10000;
for(int i=1;i<=m;i++){
scanf("%s",s+n);
int len=strlen(s+n);
for(int j=n;j<n+len;j++){
in[j]=i;
s[j]-='a'-1;
}
s[n+=len]=i+26;
n++;
}
n--;
x=a;y=b;
getsa(200);geth();
int ans=0;
while(l<=r){
int mid=l+r>>1;
if(check(mid)){l=mid+1;ans=mid;}
else r=mid-1;
}
printf("%d\n",ans);
}
return 0;
}
然后其实还有一个最小循环节和一个最长回文串,其实这两个用KMP和Manacher做就好了嘛,还快一点呢。
总结一下嘛,这类题无非就是 串接,二分答案,height分组,偶尔再来个单调栈什么的,其实做法都差不多,模型很相像,理解了height数组和sa数组的定义啊性质啊什么的就很好做(水)了。
好了是时候开启后缀自动机的大门了(还是好虚啊肿么办)