巨佬博客: https://www.cnblogs.com/zwfymqz/p/8413523.html
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e6+10; int n,M,sa[N],rk[N],tp[N],sum[N],hight[N]; char s[N]; void Qsort() { for(int i=0;i<=M;i++) sum[i]=0; for(int i=1;i<=n;i++) sum[rk[i]]++; for(int i=1;i<=M;i++) sum[i]+=sum[i-1]; for(int i=n;i>=1;i--) sa[ sum[rk[tp[i]]]-- ]=tp[i]; } void suffixsort() { M=100; for(int i=1;i<=n;i++) rk[i]=s[i]-'0'+1,tp[i]=i; Qsort(); for(int w=1,p=0;p<n;M=p,w<<=1) { p=0; for(int i=1;i<=w;i++)tp[++p]=n-w+i; for(int i=1;i<=n;i++) if(sa[i]>w)tp[++p]=sa[i]-w; Qsort(); swap(tp,rk); rk[sa[1]]=p=1; for(int i=2;i<=n;i++) rk[sa[i]]=( tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+w]==tp[sa[i]+w] )?p:++p; } } void gethight() { int j,k=0; for(int i=1;i<=n;i++) { if(k)k--; int j=sa[rk[i]-1]; while(s[i+k]==s[j+k])k++; hight[rk[i]]=k; } } int main() { scanf("%s",s+1);n=strlen(s+1); suffixsort(); for(int i=1;i<=n;i++) printf("%d ",sa[i]); }
$sa[i]:排名为i的后缀的位置$
$rk[i]:从第i个位置开始的后缀的排名,下文为了叙述方便,把从第i个位置开始的后缀简称为后缀i$
$tp[i]:基数排序的第二关键字,意义与sa一样,即第二关键字排名为i的后缀的位置$
$sum[i]:ii号元素出现了多少次。辅助基数排序$
$s:字符串,s[i]表示字符串中第i个字符串$
$height[i]:lcp(sa[i],sa[i−1]),即排名为i的后缀与排名为i−1的后缀的最长公共前缀$
Long Long Message POJ - 2774
题意:
$一个人读取同一个字符串两次,因为机器的问题,每次读取可能会在原串的左边和右边加上一串随机字符串, 问原串的最大长度$
题解:
- $将两个串合并成一个串$
- $求出height, 所以只要遍历所有的height 求出最大值即可 注意判定height所表示的第i串和第i-1串是否在两个串里$
//#include<bits/stdc++.h> #include<iostream> #include<cstdio> #include<cstring> #include<string> using namespace std; typedef long long ll; const int N=2e5+10; int n,M,sa[N],rk[N],tp[N],sum[N],height[N],lens,lens1; char s[N],s1[N]; void Qsort() { for(int i=0;i<=M;i++) sum[i]=0; for(int i=1;i<=n;i++) sum[rk[i]]++; for(int i=1;i<=M;i++) sum[i]+=sum[i-1]; for(int i=n;i>=1;i--) sa[ sum[rk[tp[i]]]-- ]=tp[i]; } void suffixsort() { M=100; for(int i=1;i<=n;i++) rk[i]=s[i]-'0'+1,tp[i]=i; Qsort(); for(int w=1,p=0;p<n;M=p,w<<=1) { p=0; for(int i=1;i<=w;i++)tp[++p]=n-w+i; for(int i=1;i<=n;i++) if(sa[i]>w)tp[++p]=sa[i]-w; Qsort(); swap(tp,rk); rk[sa[1]]=p=1; for(int i=2;i<=n;i++) rk[sa[i]]=( tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+w]==tp[sa[i]+w] )?p:++p; } } void getheight() { int j,k=0; for(int i=1;i<=n;i++) { if(k)k--; int j=sa[rk[i]-1]; while(s[i+k]==s[j+k])k++; height[rk[i]]=k; } } bool ok(int x,int y) { if(x>y)swap(x,y); return x>=1&&x<=lens&&y>=lens+1&&y<=lens+lens1; } int main() { scanf("%s",s+1); lens=strlen(s+1); scanf("%s",s1+1); lens1=strlen(s1+1); strcat(s+1,s1+1); n=lens+lens1; suffixsort();getheight();int ans=0; for(int i=2;i<=lens+lens1;i++) if(ok(sa[i],sa[i-1]))ans=max(ans,height[i]); cout<<ans; }
牛奶模式Milk Patterns P2852
题意:
$求出给定串的重复 k 次的重复子串,使之长度最大化 $
题解:
- 和上面那题非常类似 只要选k个后缀 求其最大公共前缀长度 ,并使之最大化即可
- 用单调栈跑height数组即可 单调栈维护长度为k-1的最小值
- 注意最好离散化 不然基数排序容易T
- 求相同子串肯定是和height有关
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=2e5+10; int n,M,sa[N],rk[N],tp[N],sum[N],height[N],lens,lens1; int s[N],b[N],st[N],k,ans=0; void Qsort() { for(int i=0;i<=M;i++) sum[i]=0; for(int i=1;i<=n;i++) sum[rk[i]]++; for(int i=1;i<=M;i++) sum[i]+=sum[i-1]; for(int i=n;i>=1;i--) sa[ sum[rk[tp[i]]]-- ]=tp[i]; } void suffixsort() { for(int i=1;i<=n;i++) rk[i]=s[i],tp[i]=i; Qsort(); for(int w=1,p=0;p<n;M=p,w<<=1) { p=0; for(int i=1;i<=w;i++)tp[++p]=n-w+i; for(int i=1;i<=n;i++) if(sa[i]>w)tp[++p]=sa[i]-w; Qsort(); swap(tp,rk); rk[sa[1]]=p=1; for(int i=2;i<=n;i++) rk[sa[i]]=( tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+w]==tp[sa[i]+w] )?p:++p; } } void getheight() { int j,k=0; for(int i=1;i<=n;i++) { if(k)k--; int j=sa[rk[i]-1]; while(s[i+k]==s[j+k])k++; height[rk[i]]=k; } } int main() { cin>>n>>k;k--; for(int i=1;i<=n;i++)scanf("%d",&s[i]),b[i]=s[i]; sort(b+1,b+1+n); int nn=unique(b+1,b+1+n)-b-1;M=nn; for(int i=1;i<=n;i++)s[i]=lower_bound(b+1,b+1+nn,s[i])-b; suffixsort();getheight(); int l=1,r=0; for(int i=2;i<=n;i++) { if(l<=r&&st[l]<i-k+1)l++; while(l<=r&&height[st[r]]>height[i])r--; st[++r]=i; ans=max(ans,height[st[l]]); } cout<<ans; }
[POI2000]公共串 P5546
题意:
求n个串的最长公共子串
题解:
- 之前做了两个子串的最长公共子串 只要合并起来扫一遍就行了 但是多个子串显然不能直接扫
- 首先同样将所有串接在一起 中间用特殊符号隔开
- 直接二分最长公共子串的长度即可 然后扫height数组 统计cnt(来自不同的串的个数)等于n的时候就满足条件
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=3e5+10; int n,M,sa[N],rk[N],tp[N],sum[N],height[N],lens,lens1; char s1[N]; int T,s[N],belong[N],ncnt=1,vis[N]; void Qsort() { for(int i=0;i<=M;i++) sum[i]=0; for(int i=1;i<=n;i++) sum[rk[i]]++; for(int i=1;i<=M;i++) sum[i]+=sum[i-1]; for(int i=n;i>=1;i--) sa[ sum[rk[tp[i]]]-- ]=tp[i]; } void suffixsort() { for(int i=1;i<=n;i++) rk[i]=s[i],tp[i]=i; M=40; Qsort(); for(int w=1,p=0;p<n;M=p,w<<=1) { p=0; for(int i=1;i<=w;i++)tp[++p]=n-w+i; for(int i=1;i<=n;i++) if(sa[i]>w)tp[++p]=sa[i]-w; Qsort(); swap(tp,rk); rk[sa[1]]=p=1; for(int i=2;i<=n;i++) rk[sa[i]]=( tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+w]==tp[sa[i]+w] )?p:++p; } } void getheight() { int j,k=0; for(int i=1;i<=n;i++) { if(k)k--; int j=sa[rk[i]-1]; while(s[i+k]==s[j+k])k++; height[rk[i]]=k; } } bool check(int x) { int cnt=0; for(int i=1;i<=n;i++) { if(height[i]<x)cnt=0,++ncnt; else { if(vis[belong[sa[i]]]!=ncnt) vis[belong[sa[i]]]=ncnt,cnt++; if(vis[belong[sa[i-1]]]!=ncnt) vis[belong[sa[i-1]]]=ncnt,cnt++; if(cnt==T)return true; } } return false; } int main() { cin>>T; for(int i=1;i<=T;i++) { scanf("%s",s1+1); for(int j=1;j<=strlen(s1+1);j++) { s[++n]=s1[j]-'a'+1; belong[n]=i; } s[++n]=26+i; } suffixsort();getheight(); int L=1,R=n,ans=0; while(L<=R) { int mid=L+R>>1; if(check(mid))L=mid+1,ans=mid; else R=mid-1; } cout<<ans; }
乐曲主题Musical Themes P2743
题意:
给定一个串,求其两个相同的最长子串满足
1、两个子串没有公共部分
2、子串的长度最少为5 否则输出0
3、如果一个子串的所有元素都加上一个定值等于另一个子串 那么也满足条件
题解:
- 这题和第一题有点类似 但是第一题已经固定好两个位置了,比较特殊,所以很简单
- 先不考虑条件3
- 可以二分最大长度 和上面的题目类似 维护sa的最大值和最小值 然后通过最大值和最小值的差值判断是否满足条件
- 再来看条件三 可以对串进行相邻两项做差即可
- 注意$maxx-minn>x $ 并没有等于 因为相邻的插项映射回原序列会有一个公共部分
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=3e5+10; int n,M,sa[N],rk[N],tp[N],sum[N],height[N]; int s[N],b[N]; void Qsort() { for(int i=0;i<=M;i++) sum[i]=0; for(int i=1;i<=n;i++) sum[rk[i]]++; for(int i=1;i<=n;i++) sum[i]+=sum[i-1]; for(int i=n;i>=1;i--) sa[sum[rk[tp[i]]]--]=tp[i]; } void suffixsort() { for(int i=1;i<=n;i++) rk[i]=s[i],tp[i]=i; Qsort(); for(int w=1,p=0;p<n;M=p,w<<=1) { p=0; for(int i=1;i<=w;i++) tp[++p]=n-w+i; for(int i=1;i<=n;i++) if(sa[i]>w)tp[++p]=sa[i]-w; Qsort(); swap(tp,rk); rk[sa[1]]=p=1; for(int i=2;i<=n;i++) rk[sa[i]]=( tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+w]==tp[sa[i]+w] )?p:++p; } } void getheight() { int j,k=0; for(int i=1;i<=n;i++) { if(k)k--; int j=sa[rk[i]-1]; while(s[i+k]==s[j+k])k++; height[rk[i]]=k; } } bool check(int x) { int maxx,minn; for(int i=1;i<=n;i++) { if(height[i]<x) maxx=minn=sa[i]; else { maxx=max(maxx,sa[i]); minn=min(minn,sa[i]); if(maxx-minn>x)return true; } } return false; } int main() { cin>>n; for(int i=1;i<=n;i++) scanf("%d",&s[i]); for(int i=1;i<=n-1;i++) s[i]=s[i+1]-s[i],b[i]=s[i]; n--; sort(b+1,b+1+n); M=unique(b+1,b+1+n)-b-1; for(int i=1;i<=n;i++) s[i]=lower_bound(b+1,b+1+M,s[i])-b; suffixsort(); getheight(); int L=1,R=n,ans=0; while(L<=R) { int mid=L+R>>1; if(check(mid))ans=mid,L=mid+1;else R=mid-1; } printf("%d ",ans>=4?ans+1:0); }
DISUBSTR - Distinct Substrings SP694
题意:
给定一个串,问有多少个内容不同的子串
题解:
答案为总的减去重复的
很显然总的为 n*(n+1)/2 重复的为 sum( height[i] )
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=2e3+10; int n,M,sa[N],rk[N],tp[N],sum[N],height[N]; char s[N]; void Qsort() { for(int i=0;i<=M;i++) sum[i]=0; for(int i=1;i<=n;i++) sum[rk[i]]++; for(int i=1;i<=n;i++) sum[i]+=sum[i-1]; for(int i=n;i>=1;i--) sa[sum[rk[tp[i]]]--]=tp[i]; } void suffixsort() { for(int i=1;i<=n;i++) rk[i]=s[i]-'a'+1,tp[i]=i; Qsort(); for(int w=1,p=0;p<n;M=p,w<<=1) { p=0; for(int i=1;i<=w;i++) tp[++p]=n-w+i; for(int i=1;i<=n;i++) if(sa[i]>w)tp[++p]=sa[i]-w; Qsort(); swap(tp,rk); rk[sa[1]]=p=1; for(int i=2;i<=n;i++) rk[sa[i]]=( tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+w]==tp[sa[i]+w] )?p:++p; } } void getheight() { int j,k=0; for(int i=1;i<=n;i++) { if(k)k--; int j=sa[rk[i]-1]; while(s[i+k]==s[j+k])k++; height[rk[i]]=k; } } int main() { int cas; cin>>cas; while(cas--) { scanf("%s",s+1); n=strlen(s+1); for(int i=1;i<=n;i++) if(isupper(s[i]))s[i]+=32; M=300; int ans=n*(n+1)/2; suffixsort(); getheight(); for(int i=2;i<=n;i++)ans-=height[i]; cout<<ans<<endl; } }
Sandy的卡片 P2463
题意:
见链接
题解:
- 就是最长公共串那题 和 乐曲主题那题的结合版
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=2e5+10; int n,M,sa[N],rk[N],tp[N],sum[N],height[N],T; int s[N],belong[N],b[N],a[N],vis[N],ncnt; void Qsort() { for(int i=0;i<=M;i++) sum[i]=0; for(int i=1;i<=n;i++) sum[rk[i]]++; for(int i=1;i<=n;i++) sum[i]+=sum[i-1]; for(int i=n;i>=1;i--) sa[sum[rk[tp[i]]]--]=tp[i]; } void suffixsort() { for(int i=1;i<=n;i++) rk[i]=s[i],tp[i]=i; Qsort(); for(int w=1,p=0;p<n;M=p,w<<=1) { p=0; for(int i=1;i<=w;i++) tp[++p]=n-w+i; for(int i=1;i<=n;i++) if(sa[i]>w)tp[++p]=sa[i]-w; Qsort(); swap(tp,rk); rk[sa[1]]=p=1; for(int i=2;i<=n;i++) rk[sa[i]]=( tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+w]==tp[sa[i]+w] )?p:++p; } } void getheight() { int j,k=0; for(int i=1;i<=n;i++) { if(k)k--; int j=sa[rk[i]-1]; while(s[i+k]==s[j+k])k++; height[rk[i]]=k; } } bool check(int x) { int cnt=0;++ncnt; for(int i=1;i<=n;i++) { if(height[i]<x)cnt=0,++ncnt; else { if(vis[belong[sa[i]]]!=ncnt) vis[belong[sa[i]]]=ncnt,cnt++; if(vis[belong[sa[i-1]]]!=ncnt) vis[belong[sa[i-1]]]=ncnt,cnt++; if(cnt>=T)return true; } } return false; } int main() { cin>>T; for(int i=1;i<=T;i++) { int x;scanf("%d",&x); for(int j=1;j<=x;j++) scanf("%d",&a[j]); x--; for(int j=1;j<=x;j++) a[j]=a[j+1]-a[j],s[++n]=a[j],b[n]=a[j],belong[n]=i; s[++n]=10000+i;b[n]=s[n]; } sort(b+1,b+1+n); M=unique(b+1,b+1+n)-b-1; for(int i=1;i<=n;i++) s[i]=lower_bound(b+1,b+1+M,s[i])-b; suffixsort(); getheight(); int L=1,R=n,ans=0; while(L<=R) { int mid=L+R>>1; if(check(mid))ans=mid,L=mid+1;else R=mid-1; } cout<<ans+1; return 0; }
K-th occurrence hdu6704
题意:
给定一个串S, 有m个询问: 求子串S[l,r]在S第k次出现的位置
题解:
- 求相同子串可以从height数组入手
- 首先找到该串的后缀排名pos 然后求出往右延展,也就是往排名靠后的后缀的的最右端R,使得pos-R的height均满足>=子串长度 这就满足了这些后缀的前缀一定和s[l,r]相同 向左同理
- 这个过程如何加速呢?可以考虑st表+二分 ,注意边界条件 因为height[i]是表示排名为i的串和排名为i-1的串的lcp
- 所以我们得到了一个名次范围 L-R 这些名次的后缀的前缀均满足条件
- 所以问题转化为我们要求 sa[L-R]里的第k大 用主席树维护一下就好了
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e5+10; int t[N<<5],lson[N<<5],rson[N<<5],T[N],ncnt,m; void update(int x,int l,int r,int pre,int &pos) { pos=++ncnt; t[pos]=t[pre]+1;lson[pos]=lson[pre];rson[pos]=rson[pre]; if(l==r) return ; int m=l+r>>1; if(x<=m)update(x,l,m,lson[pre],lson[pos]); else update(x,m+1,r,rson[pre],rson[pos]); } int qk(int k,int l,int r,int pre,int pos) { if(l==r)return l;int m=l+r>>1; int x=t[lson[pos]]-t[lson[pre]]; if(k<=x)return qk(k,l,m,lson[pre],lson[pos]); else return qk(k-x,m+1,r,rson[pre],rson[pos]); } int n,M,sa[N],rk[N],tp[N],sum[N],height[N],st_min[N][20],lg[N]; char s[N]; void Qsort() { for(int i=0;i<=M;i++) sum[i]=0; for(int i=1;i<=n;i++) sum[rk[i]]++; for(int i=1;i<=n;i++) sum[i]+=sum[i-1]; for(int i=n;i>=1;i--) sa[sum[rk[tp[i]]]--]=tp[i]; } void suffixsort() { for(int i=1;i<=n;i++) rk[i]=s[i],tp[i]=i; Qsort(); for(int w=1,p=0;p<n;M=p,w<<=1) { p=0; for(int i=1;i<=w;i++) tp[++p]=n-w+i; for(int i=1;i<=n;i++) if(sa[i]>w)tp[++p]=sa[i]-w; Qsort(); swap(tp,rk); rk[sa[1]]=p=1; for(int i=2;i<=n;i++) rk[sa[i]]=( tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+w]==tp[sa[i]+w] )?p:++p; } } void getheight() { int j,k=0; for(int i=1;i<=n;i++) { if(k)k--; int j=sa[rk[i]-1]; while(s[i+k]==s[j+k])k++; height[rk[i]]=k; } } int getmin(int l,int r) { int len=lg[r-l+1]; return min(st_min[l][len],st_min[r-(1<<len)+1][len]); } int solve(int l,int r,int k) { int len=r-l+1,hpos=rk[l]; int L=hpos+1,R=n,ansR=hpos; while(L<=R) { int mid=L+R>>1; if(getmin(hpos+1,mid)>=len)ansR=mid,L=mid+1; else R=mid-1; } L=1,R=hpos;int ansL=hpos; while(L<=R) { int mid=L+R>>1; if(getmin(mid,hpos)>=len)R=mid-1,ansL=mid-1; else L=mid+1; } if(ansR-ansL+1<k)return -1; return qk(k,1,1e5,T[ansL-1],T[ansR]); } int main() { lg[0]=-1; for(int i=1;i<=100000;i++)lg[i]=lg[i/2]+1; int cas;cin>>cas; while(cas--) { ncnt=0; cin>>n>>m; scanf("%s",s+1); for(int i=1;i<=n;i++)s[i]=s[i]-'a'+1; M=30; suffixsort(); getheight(); for(int i=1;i<=n;i++) update(sa[i],1,1e5,T[i-1],T[i]); for(int i=1;i<=n;i++) st_min[i][0]=height[i]; for(int i=1;i<=lg[n];i++) { for(int j=1;j+(1<<i)-1<=n;j++) st_min[j][i]=min(st_min[j][i-1],st_min[j+(1<<(i-1))][i-1]); } int l,r,k; while(m--) { scanf("%d%d%d",&l,&r,&k); printf("%d ",solve(l,r,k)); } } }