[学习笔记]后缀排序
后缀排序学多了以后就只会前缀排序了(输出1-n的整数)
【模板】后缀排序
存个板子 倍增
\(Code\ Below:\)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1000000+10;
int n,m,sa[maxn],tax[maxn],rnk[maxn],tp[maxn],h[maxn];
char a[maxn];
void SA(){
int i,k,p,f=0;m=128;
for(i=1;i<=n;i++) rnk[i]=a[i];
for(i=1;i<=m;i++) tax[i]=0;
for(i=1;i<=n;i++) tax[rnk[i]]++;
for(i=1;i<=m;i++) tax[i]+=tax[i-1];
for(i=n;i>=1;i--) sa[tax[rnk[i]]--]=i;
for(k=1,p=0;p<n;m=p,k<<=1){
p=0;
for(i=n-k+1;i<=n;i++) tp[++p]=i;
for(i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
for(i=1;i<=m;i++) tax[i]=0;
for(i=1;i<=n;i++) tax[rnk[i]]++;
for(i=1;i<=m;i++) tax[i]+=tax[i-1];
for(i=n;i>=1;i--) sa[tax[rnk[tp[i]]]--]=tp[i];
swap(rnk,tp);rnk[sa[1]]=p=1;
for(i=2;i<=n;i++) rnk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p;
}
for(i=1;i<=n;i++) rnk[sa[i]]=i;
for(i=1;i<=n;i++){
p=rnk[sa[i]-1];if(f) f--;
while(a[i+f]==a[p+f]) f++;
h[rnk[i]]=f;
}
}
signed main()
{
scanf("%s",a+1);n=strlen(a+1);
SA();
for(int i=1;i<=n;i++) printf("%d ",sa[i]);printf("\n");
return 0;
}
1、SP694 DISUBSTR - Distinct Substrings
题意:问本质不同的子串数量
其实就是减去相邻后缀的 \(LCP\),即 \(height\) 数组
\(Code\ Below:\)
#include <bits/stdc++.h>
using namespace std;
const int maxn=1000+10;
int n,m,sa[maxn],tax[maxn],rnk[maxn],tp[maxn],h[maxn],ans;
char a[maxn];
void SA(){
memset(sa,0,sizeof(sa));
memset(tax,0,sizeof(tax));
memset(rnk,0,sizeof(rnk));
memset(tp,0,sizeof(tp));
int i,k,p,f=0;m=128;
for(i=1;i<=n;i++) rnk[i]=a[i];
for(i=1;i<=m;i++) tax[i]=0;
for(i=1;i<=n;i++) tax[rnk[i]]++;
for(i=1;i<=m;i++) tax[i]+=tax[i-1];
for(i=n;i>=1;i--) sa[tax[rnk[i]]--]=i;
for(k=1,p=1;p<n;m=p,k<<=1){
p=0;
for(i=n-k+1;i<=n;i++) tp[++p]=i;
for(i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
for(i=1;i<=m;i++) tax[i]=0;
for(i=1;i<=n;i++) tax[rnk[i]]++;
for(i=1;i<=m;i++) tax[i]+=tax[i-1];
for(i=n;i>=1;i--) sa[tax[rnk[tp[i]]]--]=tp[i];
swap(rnk,tp);rnk[sa[1]]=p=1;
for(i=2;i<=n;i++) rnk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p;
}
for(int i=1;i<=n;i++) rnk[sa[i]]=i;
for(int i=1;i<=n;i++){
p=sa[rnk[i]-1];
if(f) f--;
while(a[i+f]==a[p+f]) f++;
h[rnk[i]]=f;
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
scanf("%s",a+1);n=strlen(a+1);
ans=n*(n+1)/2;SA();
for(int i=1;i<=n;i++) ans-=h[i];
printf("%d\n",ans);
}
return 0;
}
\(SP705\) 就是数据范围开大一点,\(ans\) 开个 \(long\ long\)
2、[USACO5.1]乐曲主题Musical Themes
题意:求最长重复 \(2\) 次不重叠子串
正解 \(O(n^2)\),但是我们可以用后缀数组+二分优化到 \(O(n\log n)\)
处理出 \(height\) 数组后二分长度,如果 \(height<k\) 就重置,否则处理出连续一段区间的 \(minsa\) 和 \(maxsa\),若 \(maxsa-minsa>k\) 就说明有两个子串
\(Code\ Below:\)
#include <bits/stdc++.h>
using namespace std;
const int maxn=50000+10;
int n,m,a[maxn],sa[maxn],tax[maxn],rnk[maxn],tp[maxn],h[maxn],ans;
void SA(){
int i,k,p,f=0;m=2000;
for(i=1;i<=n;i++) rnk[i]=a[i];
for(i=1;i<=m;i++) tax[i]=0;
for(i=1;i<=n;i++) tax[rnk[i]]++;
for(i=1;i<=m;i++) tax[i]+=tax[i-1];
for(i=n;i>=1;i--) sa[tax[rnk[i]]--]=i;
for(k=1,p=1;p<n;m=p,k<<=1){
p=0;
for(i=n-k+1;i<=n;i++) tp[++p]=i;
for(i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
for(i=1;i<=m;i++) tax[i]=0;
for(i=1;i<=n;i++) tax[rnk[i]]++;
for(i=1;i<=m;i++) tax[i]+=tax[i-1];
for(i=n;i>=1;i--) sa[tax[rnk[tp[i]]]--]=tp[i];
swap(rnk,tp);rnk[sa[1]]=p=1;
for(i=2;i<=n;i++) rnk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p;
}
for(int i=1;i<=n;i++) rnk[sa[i]]=i;
for(int i=1;i<=n;i++){
p=sa[rnk[i]-1];if(f) f--;
while(a[i+f]==a[p+f]) f++;
h[rnk[i]]=f;
}
}
int check(int k){
int l=sa[1],r=sa[1];
for(int i=2;i<=n;i++){
if(h[i]<k) l=r=sa[i];
else {
l=min(l,sa[i]);
r=max(r,sa[i]);
if(r-l>k) return 1;
}
}
return 0;
}
int main()
{
scanf("%d",&n);
if(n<10){
printf("0\n");
return 0;
}
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<n;i++) a[i]=a[i+1]-a[i]+1000;
a[n]=0;SA();
int l=3,r=n/2,mid;
while(l<r){
mid=(l+r+1)>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
l++;
printf("%d\n",(l>=5)?l:0);
return 0;
}
3、SP687 REPEATS - Repeats
题意:求重复次数最多的连续重叠子串
在 \(height\) 数组上瞎搞
\(Code\ Below:\)
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
using namespace std;
const int maxn=100000+10;
int n,m,RMQ[maxn][18],sa[maxn],tax[maxn],rnk[maxn],tp[maxn],h[maxn];
char a[maxn];
void SA(){
int i,k,p,f=0;m=128;
for(i=1;i<=n;i++) rnk[i]=a[i];
for(i=1;i<=m;i++) tax[i]=0;
for(i=1;i<=n;i++) tax[rnk[i]]++;
for(i=1;i<=m;i++) tax[i]+=tax[i-1];
for(i=n;i>=1;i--) sa[tax[rnk[i]]--]=i;
for(k=1,p=1;p<n;m=p,k<<=1){
p=0;
for(i=n-k+1;i<=n;i++) tp[++p]=i;
for(i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
for(i=1;i<=m;i++) tax[i]=0;
for(i=1;i<=n;i++) tax[rnk[i]]++;
for(i=1;i<=m;i++) tax[i]+=tax[i-1];
for(i=n;i>=1;i--) sa[tax[rnk[tp[i]]]--]=tp[i];
swap(rnk,tp);rnk[sa[1]]=p=1;
for(i=2;i<=n;i++) rnk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p;
}
for(int i=1;i<=n;i++) rnk[sa[i]]=i;
for(int i=1;i<=n;i++){
p=sa[rnk[i]-1];if(f) f--;
while(a[i+f]==a[p+f]) f++;
h[rnk[i]]=f;
}
for(int i=1;i<=n;i++) RMQ[i][0]=i;
for(int j=1;j<18;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
RMQ[i][j]=h[RMQ[i][j-1]]<h[RMQ[i+(1<<(j-1))][j-1]]?RMQ[i][j-1]:RMQ[i+(1<<(j-1))][j-1];
}
int query(int l,int r){
int k=log2(r-l+1);
return h[RMQ[l][k]]<h[RMQ[r-(1<<k)+1][k]]?RMQ[l][k]:RMQ[r-(1<<k)+1][k];
}
int LCP(int a,int b){
int l=rnk[a],r=rnk[b];
if(l>r) swap(l,r);
return h[query(l+1,r)];
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
#define mem(x) memset(x,0,sizeof(x))
mem(a);mem(sa);mem(tax);mem(rnk);mem(tp);mem(h);
scanf("%d",&n);
for(int i=1;i<=n;i++){
a[i]=getchar();
while(!isalpha(a[i])) a[i]=getchar();
}
SA();
int k,t,now=0,ans=0;
for(int i=1;i<n;i++){
for(int j=1;j<=n;j+=i){
k=LCP(j,j+i);
now=k/i;t=j-(i-k%i);
if(now>=0&&LCP(t,t+i)>=i-k%i) now++;
ans=max(ans,now);
}
}
printf("%d\n",ans+1);
}
return 0;
}
4、[AHOI2013]差异
题意:求 \(\frac{(n-1)n(n+1)}{2}-2\times \sum_{1\leq i<j\leq n}LCP(i,j)\)
将问题转化为所有区间 \(height\) 最小值之和,然后正着一遍单调栈,反着一遍单调栈,算一遍每个数的贡献
\(Code\ Below:\)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=500000+10;
int n,m,sa[maxn],tax[maxn],rnk[maxn],tp[maxn],h[maxn],L[maxn],R[maxn],sta[maxn],val[maxn],top;
char a[maxn];ll ans;
void SA(){
int i,k,p,f=0;m=128;
for(i=1;i<=n;i++) rnk[i]=a[i];
for(i=1;i<=m;i++) tax[i]=0;
for(i=1;i<=n;i++) tax[rnk[i]]++;
for(i=1;i<=m;i++) tax[i]+=tax[i-1];
for(i=n;i>=1;i--) sa[tax[rnk[i]]--]=i;
for(k=1,p=0;p<n;m=p,k<<=1){
p=0;
for(i=n-k+1;i<=n;i++) tp[++p]=i;
for(i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
for(i=1;i<=m;i++) tax[i]=0;
for(i=1;i<=n;i++) tax[rnk[i]]++;
for(i=1;i<=m;i++) tax[i]+=tax[i-1];
for(i=n;i>=1;i--) sa[tax[rnk[tp[i]]]--]=tp[i];
swap(rnk,tp);rnk[sa[1]]=p=1;
for(i=2;i<=n;i++) rnk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p;
}
for(i=1;i<=n;i++) rnk[sa[i]]=i;
for(i=1;i<=n;i++){
p=sa[rnk[i]-1];if(f) f--;
while(a[i+f]==a[p+f]) f++;
h[rnk[i]]=f;
}
}
signed main()
{
scanf("%s",a+1);n=strlen(a+1);
SA();ans=(ll)(n-1)*n*(n+1)/2;
for(int i=2;i<=n;i++){
L[i]=1;
while(top&&sta[top]>=h[i]) L[i]+=val[top],top--;
sta[++top]=h[i];val[top]=L[i];
}
top=0;
for(int i=n;i>=2;i--){
R[i]=1;
while(top&&sta[top]>h[i]) R[i]+=val[top],top--;
sta[++top]=h[i];val[top]=R[i];
}
for(int i=2;i<=n;i++) ans-=2ll*L[i]*R[i]*h[i];
printf("%lld\n",ans);
return 0;
}
5、[SDOI2016]生成魔咒
题意:求每一个 \(i\),字符串 \(1-i\) 中本质不同串的个数
因为字符集很大,考虑用后缀数组
我们们后面添加一个字符换成删除一个字符,那么就是在双向链表上删除两个 \(height\) 的 \(max\),保留两个 \(height\) 的 \(min\),反着来一下
\(Code\ Below:\)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=100000+10;
int n,m,a[maxn],mp[maxn],sa[maxn],tax[maxn],rnk[maxn],tp[maxn],h[maxn],pre[maxn],nxt[maxn];ll ans[maxn];
void SA(){
int i,k,p,f=0;m=n;
for(i=1;i<=n;i++) rnk[i]=a[i];
for(i=1;i<=m;i++) tax[i]=0;
for(i=1;i<=n;i++) tax[rnk[i]]++;
for(i=1;i<=m;i++) tax[i]+=tax[i-1];
for(i=n;i>=1;i--) sa[tax[rnk[i]]--]=i;
for(k=1,p=0;p<n;m=p,k<<=1){
p=0;
for(i=n-k+1;i<=n;i++) tp[++p]=i;
for(i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
for(i=1;i<=m;i++) tax[i]=0;
for(i=1;i<=n;i++) tax[rnk[i]]++;
for(i=1;i<=m;i++) tax[i]+=tax[i-1];
for(i=n;i>=1;i--) sa[tax[rnk[tp[i]]]--]=tp[i];
swap(rnk,tp);rnk[sa[1]]=p=1;
for(i=2;i<=n;i++) rnk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p;
}
for(i=1;i<=n;i++) rnk[sa[i]]=i;
for(i=1;i<=n;i++){
p=sa[rnk[i]-1];if(f) f--;
while(a[i+f]==a[p+f]) f++;
h[rnk[i]]=f;
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
mp[i]=a[i];
}
sort(mp+1,mp+n+1);
int cnt=unique(mp+1,mp+n+1)-mp-1;
for(int i=1;i<=n;i++)
a[i]=lower_bound(mp+1,mp+cnt+1,a[i])-mp;
reverse(a+1,a+n+1);
SA();
for(int i=1;i<=n;i++) pre[i]=i-1,nxt[i]=i+1;
for(int i=1;i<=n;i++){
ans[i]=(ll)(n-i+1-max(h[rnk[i]],h[nxt[rnk[i]]]));
h[nxt[rnk[i]]]=min(h[nxt[rnk[i]]],h[rnk[i]]);
h[rnk[i]]=0;
if(rnk[i]) pre[nxt[rnk[i]]]=pre[rnk[i]];
nxt[pre[rnk[i]]]=nxt[rnk[i]];
}
for(int i=n;i>=1;i--) ans[i]+=ans[i+1];
for(int i=n;i>=1;i--) printf("%lld\n",ans[i]);
return 0;
}
6、[NOI2015]品酒大会
后缀数组好题!
因为 \(r\) 相似是 \(r-1\) 相似但 \(r-1\) 相似不是 \(r\) 相似,我们考虑现将 \(height\) 从大到小排序,然后用并查集维护一下。因为 \(a_p\times a_q\) 取最大时 \(a_p\) 和 \(a_q\) 可能为负数,那么我们记录一个最大值和最小值,每次相乘一下,更新答案。最终要求的方案数是 \(num\) 的后缀和,\(a_p\times a_q\) 最大时为 \(ans\) 的后缀最大值
\(Code\ Below:\)
#include <bits/stdc++.h>
#define int long long
#define ll long long
using namespace std;
const int maxn=600000+10;
int n,m,b[maxn],val[maxn],sa[maxn],tax[maxn],rnk[maxn],tp[maxn],h[maxn],fa[maxn],siz[maxn],Max[maxn],Min[maxn];ll num[maxn],ans[maxn];
char a[maxn];
void SA(){
int i,k,p,f=0;m=128;
for(i=1;i<=n;i++) rnk[i]=a[i];
for(i=1;i<=m;i++) tax[i]=0;
for(i=1;i<=n;i++) tax[rnk[i]]++;
for(i=1;i<=m;i++) tax[i]+=tax[i-1];
for(i=n;i>=1;i--) sa[tax[rnk[i]]--]=i;
for(k=1,p=0;p<n;m=p,k<<=1){
p=0;
for(i=n-k+1;i<=n;i++) tp[++p]=i;
for(i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
for(i=1;i<=m;i++) tax[i]=0;
for(i=1;i<=n;i++) tax[rnk[i]]++;
for(i=1;i<=m;i++) tax[i]+=tax[i-1];
for(i=n;i>=1;i--) sa[tax[rnk[tp[i]]]--]=tp[i];
swap(rnk,tp);rnk[sa[1]]=p=1;
for(i=2;i<=n;i++) rnk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p;
}
for(i=1;i<=n;i++) rnk[sa[i]]=i;
for(i=1;i<=n;i++){
p=sa[rnk[i]-1];if(f) f--;
while(a[i+f]==a[p+f]) f++;
h[rnk[i]]=f;
}
}
int find(int x){
return (x==fa[x])?x:fa[x]=find(fa[x]);
}
void merge(int x,int y){
siz[y]+=siz[x];
Max[y]=max(Max[y],Max[x]);
Min[y]=min(Min[y],Min[x]);
fa[x]=y;
}
bool cmp(int x,int y){
return h[x]>h[y];
}
signed main()
{
scanf("%lld",&n);
scanf("%s",a+1);
for(int i=1;i<=n;i++){
scanf("%lld",&val[i]);
fa[i]=i;siz[i]=1;
}
for(int i=0;i<n;i++) ans[i]=-1e18;
SA();
for(int i=1;i<=n;i++) Max[rnk[i]]=Min[rnk[i]]=val[i];
for(int i=1;i<n;i++) b[i]=i+1;
sort(b+1,b+n,cmp);
int x,y;
for(int i=1;i<n;i++){
x=find(b[i]),y=find(b[i]-1);
num[h[x]]+=(ll)siz[x]*siz[y];
ans[h[x]]=max(ans[h[x]],(ll)Max[x]*Max[y]);
ans[h[x]]=max(ans[h[x]],(ll)Max[x]*Min[y]);
ans[h[x]]=max(ans[h[x]],(ll)Min[x]*Max[y]);
ans[h[x]]=max(ans[h[x]],(ll)Min[x]*Min[y]);
merge(x,y);
}
for(int i=n-2;i>=0;i--) num[i]+=num[i+1],ans[i]=max(ans[i],ans[i+1]);
for(int i=0;i<n;i++) printf("%lld %lld\n",num[i],(ans[i]==-1e18)?0:ans[i]);
return 0;
}