【新坑已填】后缀数组总结篇
首先,后缀数组的无敌功能不用我来叙述,一旦能够确定题目能够用后缀数组解决,这个题就最少成功了百分之五十,作为字符串解决方法里面,后缀数组是比较方便且很理想的解决方法,但是往往需要对题目进行分析来确定是否能够使用,而后缀数组的实现及原理,我这里就不详细的叙述,网上有学习博客,可以弄清楚模板的含义,以及sa数组,rank数组,height数组的相关用法和实现过程,这里我主要分享一下我在做后缀数组题中让我不断WA的细节以及解决办法和相关的理解及解决方法;
坑点:
1,第一次写后缀数组时,对于strlen()函数认识不是太清楚,导致一直T,而去怀疑模板的错误;(这也是犯的最脑残的错误了23333);
2,然后就是最近才遇到的问题,以前一直写的后缀数组,都是直接带着字符串跑的sa,不是说这样不行,但是作为字符串的初学者,一旦题目要求多组数据输入,在不知道字符串数组内部的情况下,贸然使用,WA都找不到WA点,所以为了保命起见的话,再输入字符串后,应该将其转化为ascii码用数组储存即可,然后为了使rank数组不出错,一般而言会在输入的字符串后面加上一个从未出现且对问题不构成影响的字符(虽然我也不知道现在这样做能解决什么问题,但就是能够玄学的避免一些错误233333);
以上只能算自己觉得很坑的地方吧,毕竟对字符串的认识还不够清晰(被队友强行拉着开着字符串的坑,谁叫队里没人做字符串哇),所以对于理解后缀数组最好的办法就是结合代码手跑一遍,然后自己去实现,逐渐琢磨一些自己不注意的小细节,对于不同人的思维不同,理解的方式不同,以上只是个人见解;
好了,现在分享一下,在后缀数组练习过程中,个人觉得思维比较强和坑点比较多的题吧:
HDU 5008:传送门
题意:求第K小的字符串的起末位置,如果有多个,就输出左端位置最小的那个;
题解:对于一串数字求第K小的思路,一般都是KMQ查一下就完了,对于字符串也是如此,你首先要确定有多少个不同的字符串以及其位置,这个可以参考《后缀数组——处理字符串的有力工具》这篇论文的介绍,然后对于每次查询就能找到字符串大致位置,然后从前往后运用height数组的特点取sa[i]最小的就是左边起点,加上已经处理过的长度,即为右端点,问题解决;(这个问题需要打个ST表方便解决问题,打错ST表调了1天的我菜的没话说23333333)
贴代码:
#include <bits/stdc++.h> #define N 100005 #define INF 0x3f3f3f3f #define ll long long using namespace std; int txt[N];int sa[N];int t1[N];int t2[N];int td[N];int rank1[N];int rank2[N]; int len;char c[N];int str[N];int mu[N]; bool cmp(int f[],int e,int w,int k){ return f[e]==f[w]&&f[e+k]==f[w+k]; } void Sa(int a[]){ int m=550; int *rank1=t1;int *td=t2; for(int i=0;i<m;i++) txt[i]=0; for(int i=0;i<len;i++){ rank1[i]=a[i];txt[a[i]]++; } for(int i=1;i<m;i++) txt[i]+=txt[i-1]; for(int i=len-1;i>=0;i--) sa[--txt[a[i]]]=i; for(int k=1;k<=len;k*=2){ int p=0; for(int i=len-k;i<len;i++) td[p++]=i; for(int i=0;i<len;i++){ if(sa[i]>=k) td[p++]=sa[i]-k; } for(int i=0;i<m;i++) txt[i]=0; for(int i=0;i<len;i++){ txt[rank1[i]]++; } for(int i=1;i<m;i++) txt[i]+=txt[i-1]; for(int i=len-1;i>=0;i--) sa[--txt[rank1[td[i]]]]=td[i]; swap(rank1,td); rank1[sa[0]]=0; p=1; for(int i=1;i<len;i++) rank1[sa[i]]=cmp(td,sa[i],sa[i-1],k)?p-1:p++; if(p==len) return ; m=p; } return ; } int h[N];int height[N]; void hh(int a[]){ for(int i=0;i<len;i++) rank2[sa[i]]=i; memset(h,0,sizeof(h)); for(int i=0;i<len;i++){ if(rank2[i]==0) continue; int t=sa[rank2[i]-1]; int w=i; int k; if(i==0||h[i-1]<=1) k=0; else { k=h[i-1]-1;w+=k;t+=k; } while(w<len&&t<len){ if(a[w]==a[t]) k++; else break; w++;t++; } h[i]=k;height[rank2[i]]=k; } return ; } typedef struct node{ int l;int r;ll date; }node; node b[4*N]; ll sum; void built(int root,int first,int end){ if(first==end){ b[root].l=first;b[root].r=end;b[root].date=len-sa[first]-height[first]-1; sum+=b[root].date; return ; } int mid=(first+end)>>1; built(root*2,first,mid); built(root*2+1,mid+1,end); b[root].l=b[root*2].l;b[root].r=b[root*2+1].r;b[root].date=b[root*2].date+b[root*2+1].date; } int sum1;ll sum2; void Q(int root,int first,int end,ll e){ if(first==end){ sum1=first;sum2=e; return ; } int mid=(first+end)>>1; if(e<=b[root*2].date) Q(root*2,first,mid,e); else Q(root*2+1,mid+1,end,e-b[root*2].date); } ll dp[N][20]; int dp1[N][20]; void RMQ(){ memset(dp,0,sizeof(dp)); memset(dp1,0,sizeof(dp1)); for(int i=1;i<len;i++) { dp[i][0]=height[i]; dp1[i][0]=sa[i]; } for(int j=1;j<=mu[len-1];j++){ for(int i=1;i+(1<<j)-1<len;i++){ dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]); dp1[i][j]=min(dp1[i][j-1],dp1[i+(1<<(j-1))][j-1]); } } } bool LCA(int l,int r,ll t1){ if(l<r) swap(l,r); // if(l==r) return r++; int t=mu[l-r+1]; if(t==-1) return 0; ll t2=min(dp[r][t],dp[l-(1<<t)+1][t]); if(t2>=t1) return 1; return 0; } int LCA1(int l,int r){ int k=mu[r-l+1]; return min(dp1[l][k],dp1[r-(1<<k)+1][k]); } int slove(int l,ll t){ int l1=l+1;int r1=len-1; int ans; while(l1<=r1){ int mid=(l1+r1)>>1; if(LCA(l,mid,t)) ans=mid,l1=mid+1; else r1=mid-1; } if(LCA(l,ans,t)) return LCA1(l,ans); return sa[l]; } int main(){ mu[0]=-1; for(int i=1;i<=N;i++){ if((i&(i-1))==0) mu[i]=mu[i-1]+1; else mu[i]=mu[i-1]; } while(scanf("%s",c)!=EOF){ len=strlen(c); for(int i=0;i<len;i++) str[i]=c[i]; str[len]='#'; len++; Sa(str);hh(str); sum=0; RMQ(); built(1,1,len-1); // cout<<"======="<<" "<<sum<<endl; int k; scanf("%d",&k); int l=0;int r=0; ll t1; for(int i=1;i<=k;i++){ scanf("%I64d",&t1); t1=((long long)(l^r^t1)+1); // cout<<"异或 :"<<" "<<t1<<endl; if(t1>sum){ l=r=0; printf("%d %d\n",l,r); } else{ Q(1,1,len-1,t1); ll t2=height[sum1]+sum2; l=sa[sum1]; int tt=slove(sum1,t2); if(l>tt) l=tt; r=t2+l-1; l++;r++; printf("%d %d\n",l,r); } } } return 0; }
HDU 5769:传送门
题意:给你一个字符和一个字符串,求至少包含一个这个字符的所有不同子串的个数;
题解:相对而言比较水的一个题吧,裸套后缀数组,但是我刚开始做这个题时直接跑后缀数组,只处理了首字母为这个字符的情况,而对于这个题目而言他可能出现把限制字符夹在中间的情况,所以对于每个后缀你要找到最先出现限制字符的位置以及heght数组中的最大值,按照寻找不同子串的类似方法维护一下即可,不过加了一个限制条件:
贴代码:
#include <bits/stdc++.h> #define N 100005 using namespace std; int txt[N];int t1[N];int t2[N];int rank1[N];int rank2[N];int sa[N]; int len;char c[N];int str[N]; bool cmp(int f[],int e,int w,int k){ return f[e]==f[w]&&f[e+k]==f[w+k]; } void Sa(int a[]){ int m=250; int *rank1=t1;int *td=t2; for(int i=0;i<m;i++) txt[i]=0; for(int i=0;i<len;i++){ rank1[i]=a[i];txt[a[i]]++; } for(int i=1;i<m;i++) txt[i]+=txt[i-1]; for(int i=len-1;i>=0;i--) sa[--txt[a[i]]]=i; for(int k=1;k<=len;k*=2){ int p=0; for(int i=len-k;i<len;i++) td[p++]=i; for(int i=0;i<len;i++){ if(sa[i]>=k) td[p++]=sa[i]-k; } for(int i=0;i<m;i++) txt[i]=0; for(int i=0;i<len;i++) txt[rank1[i]]++; for(int i=1;i<m;i++) txt[i]+=txt[i-1]; for(int i=len-1;i>=0;i--) sa[--txt[rank1[td[i]]]]=td[i]; swap(rank1,td); rank1[sa[0]]=0; p=1; for(int i=1;i<len;i++) rank1[sa[i]]=cmp(td,sa[i],sa[i-1],k)?p-1:p++; if(p==len) return ; m=p; } return ; } int h[N];int height[N]; void hh(int a[]){ for(int i=0;i<len;i++) rank2[sa[i]]=i; memset(h,0,sizeof(h)); for(int i=0;i<len;i++){ if(rank2[i]==0) continue; int t=sa[rank2[i]-1];int w=i;int k; if(i==0||h[i-1]<=1) k=0; else k=h[i-1]-1; t+=k;w+=k; while(t<len&&w<len){ if(a[t]==a[w]) k++; else break; t++;w++; } h[i]=k;height[rank2[i]]=k; } return ; } bool vis[N];int vi[N]; long long slove(){ long long ans=0;int flag=0; // long long sum=0; memset(vi,0,sizeof(vi)); memset(vis,0,sizeof(vis)); int u=0; for(int i=1;i<len;i++){ if(height[i]==0&&flag!=0) break; if(sa[i]==0) { flag=1;continue; } if(flag!=0){ u++;vi[u]=sa[i]; vis[sa[i]]=1; if(flag==1) ans+=(len-1-sa[i]); else ans+=(len-1-sa[i]-height[i]); flag++; } } // cout<<ans<<endl; sort(vi+1,vi+u+1); for(int i=2;i<len;i++){ int l=1;int r=u; if(sa[i]<2) continue; if(vis[sa[i]]==1) continue; // cout<<i<<" "<<sa[i]<<endl; while(l<=r){ int mid=(l+r)>>1; if(vi[mid]>sa[i]) r=mid-1; else l=mid+1; } if(l>u) continue; int t; if(l==0) t=0; else t=vi[l]; ans+=(len-1-sa[i]-max(t-sa[i],height[i])); } return ans; } int main(){ int T; scanf("%d",&T); getchar(); char ch; int Case=0; while(T--){ Case++; scanf(" %c",&ch); len=0; str[len]=ch; len++;str[len]='#'; scanf("%s",c); int len1=strlen(c); len++; for(int i=0;i<len1;i++) str[len++]=c[i]; str[len]='$';len++; Sa(str);hh(str); long long tt=slove(); printf("Case #%d: ",Case); printf("%lld\n",tt); } return 0; }
好吧,再补上另外一种类型的,多个字符串的处理问题:
POJ 3450:传送门
题意:给你N个字符串,要你找出最长公共连续子串,如果存在多个,输出字典序最小的那个.
题解:把n个字符串和成一个字符串处理,中间用不同的字符间隔开即可,跑后缀数组,二分答案,根据sa数组的特性先满足条件的一定是字典序最小的,所以只需二分答案,找到最长的满足条件的子串即为所求,特点是如何判断满足条件,这里大概有两种处理方法,个人认为用一个数组记录每个字符出现的位置即能达到目的:
贴代码:
#include <iostream> #include <algorithm> #include <map> #include <set> #include <cstring> #include <cstdio> #include <vector> #include <stack> #include <queue> #define N 1000050 using namespace std; int sa[N];int rank1[N];int rank2[N];int txt[N];int td[N];int t1[N];int t2[N]; int len;char b[N];int str[N]; int n; bool cmp(int f[],int e,int w,int k){ return f[w]==f[e]&&f[w+k]==f[e+k]; } void Sa(int a[]){ int m=5000; int *rank1=t1;int *td=t2; for(int i=0;i<m;i++) txt[i]=0; for(int i=0;i<len;i++){ rank1[i]=a[i];txt[a[i]]++; } for(int i=1;i<m;i++) txt[i]+=txt[i-1]; for(int i=len-1;i>=0;i--) sa[--txt[a[i]]]=i; for(int k=1;k<=len;k*=2){ int p=0; for(int i=len-k;i<len;i++) td[p++]=i; for(int i=0;i<len;i++){ if(sa[i]>=k) td[p++]=sa[i]-k; } for(int i=0;i<m;i++) txt[i]=0; for(int i=0;i<len;i++) txt[rank1[i]]++; for(int i=1;i<m;i++) txt[i]+=txt[i-1]; for(int i=len-1;i>=0;i--) sa[--txt[rank1[td[i]]]]=td[i]; swap(rank1,td); rank1[sa[0]]=0; p=1; for(int i=1;i<len;i++) rank1[sa[i]]=cmp(td,sa[i],sa[i-1],k)?p-1:p++; if(p==len) return ; m=p; } return ; } int h[N];int height[N]; void hh(int a[]){ for(int i=0;i<len;i++) rank2[sa[i]]=i; memset(h,0,sizeof(h)); for(int i=0;i<len;i++){ if(rank2[i]==0) continue; int t=sa[rank2[i]-1]; int w=i; int k; if(i==0||h[i-1]<=1) k=0; else { k=h[i-1]-1;t+=k;w+=k; } while(t<len&&w<len){ if(a[t]==a[w]) k++; else break; t++;w++; } h[i]=k;height[rank2[i]]=k; } return ; } bool vis[4005]; int Len[N]; int pos; bool check(int t){ for(int i=0;i<=n;i++) vis[i]=0; vis[0]=1; int ans=0; for(int i=1;i<len;i++){ if(height[i]>=t){ if(!vis[Len[sa[i-1]]]){ ans++;vis[Len[sa[i-1]]]=1; } if(!vis[Len[sa[i]]]){ ans++;vis[Len[sa[i]]]=1; } if(ans==n) { pos=sa[i];return 1; } } else{ ans=0; for(int i=0;i<=n;i++) vis[i]=0; vis[0]=1; } } return 0; } int slove(){ int l=1;int r=205; while(l<=r){ int mid=(l+r)>>1; if(check(mid)) l=mid+1; else r=mid-1; } return r; } int main(){ while(scanf("%d",&n)==1&&n){ int len1=0; int j=0; for(int i=1;i<=n;i++){ scanf("%s",len1+b); for(;b[len1]!='\0';len1++) { str[len1]=b[len1]+4000; Len[len1]=i; } str[len1]=++j; Len[len1]=0; len1++; } len=len1; Sa(str);hh(str); int t1=slove(); if(t1==0) printf("IDENTITY LOST\n"); else{ for(int i=pos;i<pos+t1;i++){ printf("%c",str[i]-4000); } printf("\n"); } } return 0;