后缀数组(无讲解)
Posted suika
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了后缀数组(无讲解)相关的知识,希望对你有一定的参考价值。
BZO2754: [SCOI2012]喵星球上的点名
分析:
- 把姓和名中间用一个分隔符分开,和询问串一起建立后缀数组。
- 后缀数组上每个位置存对应串的标号。对于一个询问串(T),找到他对应的位置。
- 考虑和他的lcp>=len(T)的位置都是合法的。左右二分/倍增提取出这样的区间。
- 那么第一问转化成了区间颜色数量。树状数组即可。
- 第二问相当于有若干次区间 只在第一次出现的位置 加。然后查询每个位置的和。方法类似,不过这次我们扫一遍序列。
- 假设当前位置为i。先加入左端点是i的询问,在i处+1。然后处理这个点的信息。ans[id[i]] += [pre,i]的区间和(pre是上一次和i相等的位置)然后处理右端点等于i的询问,在该询问的左端点处-1。
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define rep(n) for(i=1;i<=n;i++)
#define per(n) for(i=n;i;i--)
#define N 500050
int w[N],sa[N],ht[N],rk[N],f[20][N],n,m,ln;
int bg[N],ed[N],ws[N],wa[N],wb[N],Lg[N],id[N];
int ql[N],qr[N],ans1[N],now[N],nxt[N],c[N],pid[N];
int ans2[N],ke[N];
void fix(int x,int v) {
for(;x<=ln;x+=x&(-x)) c[x]+=v;
}
int inq(int x) {
int re=0;
for(;x;x-=x&(-x)) re+=c[x]; return re;
}
struct A {
int l,r,id;
bool operator < (const A &x) const {
return l == x.l ? r < x.r : l < x.l;
}
}q[N];
priority_queue<pair<int,int> >pq;
void build_sa(int n,int m) {
int i,j,p,*x=wa,*y=wb;
rep(m) ws[i]=0;
rep(n) ws[x[i]=w[i]]++;
rep(m) ws[i]+=ws[i-1];
per(n) sa[ws[x[i]]--]=i;
for(j=1;j<n;j<<=1) {
p=0;
for(i=n;i>n-j;i--) y[++p]=i;
rep(n) if(sa[i]>j) y[++p]=sa[i]-j;
rep(m) ws[i]=0;
rep(n) ws[x[i]]++;
rep(m) ws[i]+=ws[i-1];
per(n) sa[ws[x[y[i]]]--]=y[i];
swap(x,y);
m=1;
rep(n) {
x[sa[i]]=(y[sa[i]]==y[sa[i+1]]&&y[sa[i]+j]==y[sa[i+1]+j])?m:m++;
}
if(m>n) break;
}
rep(n) rk[sa[i]]=i;
p=0;
rep(n) if(rk[i]!=n) {
j=rk[i]+1;
for(;w[i+p]==w[sa[j]+p];p++) ;
ht[j]=p;
if(p) p--;
}
Lg[0]=-1;
rep(n) Lg[i]=Lg[i>>1]+1,f[0][i]=ht[i];
for(i=1;(1<<i)<=n;i++) {
for(j=1;j<=n-(1<<i)+1;j++) {
f[i][j]=min(f[i-1][j],f[i-1][j+(1<<(i-1))]);
}
}
}
int gm(int l,int r) {
int len=Lg[r-l+1];return min(f[len][l],f[len][r-(1<<len)+1]);
}
int lcp(int x,int y) {
x=rk[x], y=rk[y];
if(x>y) swap(x,y);
if(x==y) return ln-sa[x]+1;
return gm(x+1,y);
}
void prt(int *a) {
int i;
for(i=1;i<=ln;i++) printf("%d ",a[i]); puts("");
}
int main() {
scanf("%d%d",&n,&m);
int i,x,y,j;
int tmp=10000;
for(i=1;i<=n;i++) {
scanf("%d",&x);
bg[i]=ln+1;
while(x--) {
scanf("%d",&y); y++;
w[++ln]=y;
}
w[++ln]=++tmp;
scanf("%d",&x);
while(x--) {
scanf("%d",&y); y++;
w[++ln]=y;
}
ed[i]=ln;
w[++ln]=++tmp;
}
for(i=1;i<=m;i++) {
scanf("%d",&x);
bg[i+n]=ln+1;
while(x--) {
scanf("%d",&y); y++;
w[++ln]=y;
}
ed[i+n]=ln;
w[++ln]=++tmp;
}
build_sa(ln,tmp);
for(i=1;i<=n;i++) for(j=bg[i];j<=ed[i];j++) id[j]=i;
for(i=1;i<=ln;i++) pid[i]=id[sa[i]];
// for(i=1;i<=m;i++) for(j=bg[i+n];j<=ed[i+n];j++) id[j]=i+n;
//prt(id);
//prt(w);
for(i=1;i<=m;i++) {
x=rk[bg[i+n]];
int len=ed[i+n]-bg[i+n]+1;
//printf("x=%d len=%d
",x,len);
int l=1,r=x;
while(l<r) {
int mid=(l+r)>>1;
if(x==mid||gm(mid+1,x)>=len) r=mid;
else l=mid+1;
}
ql[i]=l;
l=x,r=ln+1;
while(l<r) {
int mid=(l+r)>>1;
if(x==mid||gm(x+1,mid)>=len) l=mid+1;
else r=mid;
}
qr[i]=l-1;
q[i]=(A){ql[i],qr[i],i};
/*printf("Q%d
",i);
printf("ql=%d, qr=%d
",ql[i],qr[i]);
for(j=ql[i];j<=qr[i];j++) {
printf("%d
",id[sa[j]]);
}
puts("");*/
}
sort(q+1,q+m+1);
for(i=ln;i;i--) {
nxt[i]=now[pid[i]];
now[pid[i]]=i;
}
memset(now,0,sizeof(now));
for(i=1;i<=ln;i++) {
if(pid[i]&&!now[pid[i]]) {
now[pid[i]]=1;
fix(i,1);
}
}
j=1;
for(i=1;i<=m;i++) {
for(;j<q[i].l;j++) {
if(pid[j]) {
fix(j,-1);
if(nxt[j]) fix(nxt[j],1);
}
}
ans1[q[i].id]=inq(q[i].r);
}
for(i=1;i<=m;i++) printf("%d
",ans1[i]);
memset(now,0,sizeof(now));
memset(c,0,sizeof(c));
j=1;
for(i=1;i<=ln;i++) {
for(;q[j].l==i;j++) fix(i,1),pq.push(make_pair(-q[j].r,q[j].l));
if(pid[i]) {
ans2[pid[i]]+=inq(i)-inq(now[pid[i]]);
now[pid[i]]=i;
}
while(!pq.empty()&&pq.top().first==-i) {
fix(pq.top().second,-1); pq.pop();
}
}
for(i=1;i<=n;i++) printf("%d ",ans2[i]);
}
/*
2 3
6 8 25 0 24 14 8 6 18 0 10 20 24 0
7 14 17 8 7 0 17 0 5 8 25 0 24 0
4 8 25 0 24
4 7 0 17 0
4 17 0 8 25
*/
BZOJ4556: [Tjoi2016&Heoi2016]字符串
- 二分答案mid。然后像上一道题那样,根据[c,d]找一个合法区间ht>=mid。是否存在一个位置的a<=sa[i]<=b-mid+1。主席树上查即可。
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <cmath>
#include <map>
#include <queue>
#include <stack>
#include <cctype>
#include <vector>
#include <set>
#include <string>
using namespace std;
#define N 100050
#define rep(n) for(i=1;i<=n;i++)
#define per(n) for(i=n;i;i--)
char w[N];
int rr[N],rk[N],sa[N],ht[N],f[20][N],n,m;
int ws[N],wa[N],wb[N],Lg[N],root[N];
int siz[N*30],ls[N*30],rs[N*30],cnt;
void update(int l,int r,int x,int &p,int q) {
p=++cnt; ls[p]=ls[q]; rs[p]=rs[q]; siz[p]=siz[q]+1;
if(l==r) return ;
int mid=(l+r)>>1;
if(x<=mid) update(l,mid,x,ls[p],ls[q]);
else update(mid+1,r,x,rs[p],rs[q]);
}
void build_sa(int n,int m) {
int i,j,p,*x=wa,*y=wb;
rep(m)ws[i]=0;
rep(n)ws[x[i]=rr[i]]++;
rep(m)ws[i]+=ws[i-1];
per(n)sa[ws[x[i]]--]=i;
for(j=1;j<n;j<<=1) {
p=0;
for(i=n;i>n-j;i--) y[++p]=i;
rep(n) if(sa[i]>j) y[++p]=sa[i]-j;
rep(m) ws[i]=0;
rep(n) ws[x[i]]++;
rep(m) ws[i]+=ws[i-1];
per(n) sa[ws[x[y[i]]]--]=y[i];
swap(x,y); m=1;
rep(n) {
x[sa[i]]=(y[sa[i]]==y[sa[i+1]]&&y[sa[i]+j]==y[sa[i+1]+j])?m:m++;
}
if(m>n) break;
}
rep(n) rk[sa[i]]=i;
p=0;
rep(n) if(rk[i]!=n) {
j=rk[i]+1;
for(;w[i+p]==w[sa[j]+p];p++) ;
ht[j]=p;
if(p) p--;
}
Lg[0]=-1;
rep(n) Lg[i]=Lg[i>>1]+1,f[0][i]=ht[i];
for(i=1;(1<<i)<=n;i++) {
for(j=1;j+(1<<i)-1<=n;j++) {
f[i][j]=min(f[i-1][j],f[i-1][j+(1<<(i-1))]);
}
}
}
int gm(int l,int r) {
int len=Lg[r-l+1];
return min(f[len][l],f[len][r-(1<<len)+1]);
}
int l1,l2,r1,r2,OK;
void query(int l,int r,int x,int y,int p,int q) {
if(OK) return ;
if(x<=l&&y>=r) {
if(siz[q]-siz[p]>0) OK=1;
return ;
}
int mid=(l+r)>>1;
if(x<=mid) query(l,mid,x,y,ls[p],ls[q]);
if(y>mid) query(mid+1,r,x,y,rs[p],rs[q]);
}
bool check(int mid) {
int x=rk[l2];
int d=0,p=x,ql,qr;
for(d=1;x-d>=1&&gm(x-d+1,x)>=mid;d<<=1) ;
for(d>>=1;d;d>>=1) {
if(p-d>=1&&gm(p-d+1,x)>=mid) p-=d;
}
ql=p;
d=0,p=x;
for(d=1;x+d<=n&&gm(x+1,x+d)>=mid;d<<=1) ;
for(d>>=1;d;d>>=1) {
if(p+d<=n&&gm(x+1,p+d)>=mid) p+=d;
}
qr=p;
OK=0;
query(1,n,l1,r1-mid+1,root[ql-1],root[qr]);
return OK;
}
int main() {
scanf("%d%d%s",&n,&m,w+1);
int i;
for(i=1;i<=n;i++) rr[i]=w[i]-'a'+1;
build_sa(n,255);
rep(n) {
update(1,n,sa[i],root[i],root[i-1]);
}
while(m--) {
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
int l=0,r=min(r1-l1,r2-l2)+2;
while(l<r) {
int mid=(l+r)>>1;
if(check(mid)) l=mid+1;
else r=mid;
}
printf("%d
",l-1);
}
}
BZOJ4199: [Noi2015]品酒大会
分析:
- 把sa[]按ht排序。并查集维护即可。
- 维护连通块内方案数和最大值。由于有负数需要再维护一个最小值。
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
using namespace std;
#define N 300050
typedef long long ll;
#define rep(n) for(i=1;i<=n;i++)
#define per(n) for(i=n;i;i--)
int rr[N],n,val[N],sa[N],rk[N],ht[N],ws[N],wa[N],wb[N];
int t[N],siz[N],fa[N];
ll ans1[N],ans2[N];
int mn[N],mx[N];
char w[N];
int find(int x) {return fa[x]==x?x:fa[x]=find(fa[x]);}
void build_sa(int n,int m) {
int i,j,p,*x=wa,*y=wb;
rep(m) ws[i]=0;
rep(n) ws[x[i]=rr[i]]++;
rep(m) ws[i]+=ws[i-1];
per(n) sa[ws[x[i]]--]=i;
for(j=1;j<n;j<<=1) {
p=0;
for(i=n;i>n-j;i--) y[++p]=i;
rep(n) if(sa[i]>j) y[++p]=sa[i]-j;
rep(m) ws[i]=0;
rep(n) ws[x[i]]++;
rep(m) ws[i]+=ws[i-1];
per(n) sa[ws[x[y[i]]]--]=y[i];
swap(x,y);
m=1;
rep(n) {
x[sa[i]]=(y[sa[i]]==y[sa[i+1]]&&y[sa[i]+j]==y[sa[i+1]+j])?m:m++;
}
if(m>n) break;
}
rep(n) rk[sa[i]]=i;
p=0;
rep(n) if(rk[i]!=n) {
j=rk[i]+1;
for(;w[i+p]==w[sa[j]+p];p++) ;
ht[j]=p;
if(p) p--;
}
}
inline bool cmp(const int &x,const int &y) {return ht[x]>ht[y];}
int main() {
scanf("%d%s",&n,w+1);
int i;
rep(n) ans2[i]=-1ll<<60;
for(i=1;i<=n;i++) scanf("%d",&val[i]);
for(i=1;i<=n;i++) rr[i]=w[i]-'a'+1;
build_sa(n,255);
for(i=1;i<=n;i++) t[i]=i,fa[i]=i,mn[i]=mx[i]=val[sa[i]],siz[i]=1;
sort(t+1,t+n+1,cmp);
for(i=1;i<=n;i++) {
int x=t[i],y=x-1;
if(x==1) continue;
int dx=find(x),dy=find(y);
ans1[ht[x]]+=ll(siz[dx])*siz[dy];
ans2[ht[x]]=max(ans2[ht[x]],max(ll(mn[dx])*mn[dy],ll(mx[dx])*mx[dy]));
fa[dx]=dy; siz[dy]+=siz[dx]; mn[dy]=min(mn[dy],mn[dx]); mx[dy]=max(mx[dy],mx[dx]);
}
per(n) ans1[i-1]+=ans1[i],ans2[i-1]=max(ans2[i-1],ans2[i]);
rep(n) printf("%lld %lld
",ans1[i-1],ans1[i-1]?ans2[i-1]:0);
}
BZOJ3230: 相似子串
分析:
- 考虑sa数组每一位都对应一些本质不同字符串。这个肯定是按字典序来的。
- 维护n-sa[i]+1-ht[i]的前缀和。每次询问二分即可。
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
using namespace std;
#define N 100050
typedef long long ll;
#define rep(n) for(i=1;i<=n;i++)
#define per(n) for(i=n;i;i--)
#define pi pair<int,int>
int n,m;
ll v[N],sv[N];
char w[N];
struct SA {
int rr[N],sa[N],ht[N],ws[N],wa[N],wb[N],rk[N],Lg[N],f[20][N];
void build_sa(int n,int m) {
int i,j,p,*x=wa,*y=wb;
rep(m) ws[i]=0;
rep(n) ws[x[i]=rr[i]]++;
rep(m) ws[i]+=ws[i-1];
per(n) sa[ws[x[i]]--]=i;
for(j=1;j<n;j<<=1) {
p=0;
for(i=n;i>n-j;i--) y[++p]=i;
rep(n) if(sa[i]>j) y[++p]=sa[i]-j;
rep(m) ws[i]=0;
rep(n) ws[x[i]]++;
rep(m) ws[i]+=ws[i-1];
per(n) sa[ws[x[y[i]]]--]=y[i];
swap(x,y);
m=1;
rep(n) x[sa[i]]=(y[sa[i]]==y[sa[i+1]]&&y[sa[i]+j]==y[sa[i+1]+j])?m:m++;
if(m>n) break;
}
rep(n) rk[sa[i]]=i;
p=0;
rep(n) if(rk[i]!=n) {
j=rk[i]+1;
for(;rr[i+p]==rr[sa[j]+p];p++) ;
ht[j]=p;
if(p) p--;
}
Lg[0]=-1;
rep(n) Lg[i]=Lg[i>>1]+1;
rep(n) f[0][i]=ht[i];
for(i=1;(1<<i)<=n;i++) for(j=1;j+(1<<i)-1<=n;j++) f[i][j]=min(f[i-1][j],f[i-1][j+(1<<(i-1))]);
}
int gm(int l,int r) {
int len=Lg[r-l+1];
return min(f[len][l],f[len][r-(1<<len)+1]);
}
pi getk(ll K) {
int l=1,r=n+1;
while(l<r) {
int mid=(l+r)>>1;
if(sv[mid]>=K) r=mid;
else l=mid+1;
}
return make_pair(sa[l],int(n-sv[l]+K));
}
int LCP(int x,int y) {
if(x==y) return n-x+1;
x=rk[x],y=rk[y];
if(x>y) swap(x,y);
return gm(x+1,y);
}
int lcp(int l1,int r1,int l2,int r2) {
int t=LCP(l1,l2);
//printf("%d %d %d %d
",l1,r1,l2,r2);
//printf("t=%d
",t);
return min(t,min(r1-l1+1,r2-l2+1));
}
}s1,s2;
ll SQ(ll x) {return x*x;}
int main() {
scanf("%d%d%s",&n,&m,w+1);
int i;
rep(n) s1.rr[i]=w[i]-'a'+1;
rep(n) s2.rr[i]=s1.rr[n-i+1];
s1.build_sa(n,255);
s2.build_sa(n,255);
rep(n) {
v[i]=n-s1.sa[i]+1-s1.ht[i];
sv[i]=sv[i-1]+v[i];
}
while(m--) {
ll x,y;
scanf("%lld%lld",&x,&y);
if(x>sv[n]||y>sv[n]) {
puts("-1"); continue;
}
pi tx=s1.getk(x);
pi ty=s1.getk(y);
int l1=tx.first,r1=tx.second;
int l2=ty.first,r2=ty.second;
//printf("%d %d %d %d
",l1,r1,l2,r2);
printf("%lld
",SQ(s1.lcp(l1,r1,l2,r2))+SQ(s2.lcp(n-r1+1,n-l1+1,n-r2+1,n-l2+1)));
}
}
以上是关于后缀数组(无讲解)的主要内容,如果未能解决你的问题,请参考以下文章