后缀自动机部分的练习。
毒奶(冬令营一定不会考SAM的)
然后就没了。
https://vjudge.net/contest/208780#overview
是一个感性理解比较容易的东西。
证明…可以去找clj的课件看。
有很多奇怪的性质,稍微记一下。
Problem A
应该是可以写O((n+Q)logn)或者O((n+Q)log^2n)的方法的..
O(n^2+Q),暴力对每一个右端点为结束的位置遍历一遍当前的SAM求答案。
然后差分。
Code
#include<bits/stdc++.h>
using namespace std;
#define Death Komachi
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define M 2004
char S[M];
int T,n,Ans[M][M];
struct SAM{
static const int N=4004;
int Trans[N][26],Slink[N],Mxlen[N],R[N],tot;
void Clear(){
memset(Trans,0,sizeof(Trans));
memset(R,0,sizeof(R));
}
int Extend(int v,int c){
int z=++tot;
Mxlen[z]=Mxlen[v]+1;
R[z]=R[v]+1;
while(v && !Trans[v][c])Trans[v][c]=z,v=Slink[v];
if(!v)Slink[z]=1;
else {
int x=Trans[v][c];
if(Mxlen[v]+1==Mxlen[x])Slink[z]=x;
else{
int y=++tot;
Slink[y]=Slink[x];
Mxlen[y]=Mxlen[v]+1;
R[y]=R[z];
memcpy(Trans[y],Trans[x],sizeof(Trans[x]));
while(Trans[v][c]==x)Trans[v][c]=y,v=Slink[v];
Slink[x]=Slink[z]=y;
}
}
return z;
}
int Q[N],Deg[N];
void Build(char *S,int len){
int p=tot=1;
REP(i,0,len){
p=Extend(p,S[i]-'a');
int l,r;l=r=0;
REP(j,2,tot+1)Deg[Slink[j]]++;
REP(j,1,tot+1)if(!Deg[j])Q[r++]=j;
while(l<r){
int A=Q[l++],F=Slink[A];
if(A==1)break;
R[F]=max(R[F],R[A]);
Ans[i][R[A]-Mxlen[A]]++,Ans[i][R[A]-Mxlen[F]]--;
if(!(--Deg[F]))Q[r++]=F;
}
REP(j,1,i+1)Ans[i][j]+=Ans[i][j-1];
DREP(j,i-1,-1)Ans[i][j]+=Ans[i][j+1];
}
}
}SAM;
int main(){
scanf("%d",&T);
while(T--){
memset(Ans,0,sizeof(Ans));
SAM.Clear();
scanf("%s",S);
n=strlen(S);
SAM.Build(S,n);
int Q,l,r;
scanf("%d",&Q);
while(Q--){
scanf("%d%d",&l,&r);
printf("%d\n",Ans[r-1][l-1]);
}
}
return 0;
}
Problem C
LCS,将第二个串放在第一个串上跑一下即可。
然后就是SAM求前缀的LCP的过程。
Code
#include<bits/stdc++.h>
using namespace std;
#define Death Komachi
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define M 100004
#define LL long long
int n;
char S[M];
struct SAM{
static const int N=M<<1;
int Trans[N][26],Slink[N],Mxlen[N],tot;
void Clear(){
REP(i,1,tot+1)memset(Trans[i],0,sizeof(Trans[i]));
}
int Extend(int v,int c){
int z=++tot;
Mxlen[z]=Mxlen[v]+1;
while(v && !Trans[v][c])Trans[v][c]=z,v=Slink[v];
if(!v)Slink[z]=1;
else {
int x=Trans[v][c];
if(Mxlen[x]==Mxlen[v]+1)Slink[z]=x;
else{
int y=++tot;
Slink[y]=Slink[x];
Mxlen[y]=Mxlen[v]+1;
memcpy(Trans[y],Trans[x],sizeof(Trans[x]));
while(Trans[v][c]==x)Trans[v][c]=y,v=Slink[v];
Slink[x]=Slink[z]=y;
}
}
return z;
}
void Build(char *S,int len){
int p=tot=1;
REP(i,0,len)p=Extend(p,S[i]-'a');
}
void Query(char *S,int len){
int u=1,l=0,Ans=0;
REP(i,0,len){
int c=S[i]-'a';
while(u && !Trans[u][c])u=Slink[u],l=Mxlen[u];
if(u)u=Trans[u][c],l++;
else u=1,l=0;
Ans=max(Ans,l);
}
printf("%d\n",Ans);
}
}SAM;
int main(){
while(scanf("%s",S)!=EOF){
SAM.Clear();
SAM.Build(S,strlen(S));
scanf("%s",S);
SAM.Query(S,strlen(S));
}
return 0;
}
Problem G
查询当前串出现次数大于等于K的子串数目。
注意到Slink树上次数的单调性。
离线,用并查集来维护删点即可。
#include<bits/stdc++.h>
using namespace std;
#define Death Komachi
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define M 250004
#define LL long long
char S[M];
int n,K,m;
int Op[M],Pos[M];
LL Ans[M],Res;
struct SAM{
static const int N=M<<1;
int Trans[N][26],Slink[N],Mxlen[N],Sz[N],tot;
void Clear(){
REP(i,1,tot+1)memset(Trans[i],Sz[i]=Del[i]=0,sizeof(Trans[i]));
}
int Extend(int v,int c){
int z=++tot;
Sz[z]=1;
Mxlen[z]=Mxlen[v]+1;
while(v && !Trans[v][c])Trans[v][c]=z,v=Slink[v];
if(!v)Slink[z]=1;
else {
int x=Trans[v][c];
if(Mxlen[x]==Mxlen[v]+1)Slink[z]=x;
else {
int y=++tot;
Slink[y]=Slink[x];
Mxlen[y]=Mxlen[v]+1;
memcpy(Trans[y],Trans[x],sizeof(Trans[x]));
while(Trans[v][c]==x)Trans[v][c]=y,v=Slink[v];
Slink[x]=Slink[z]=y;
}
}
return z;
}
int Deg[N],Q[N],Fa[N],Del[N];
int From(int a){return Fa[a]==a?a:Fa[a]=From(Fa[a]);}
void Delete(int x){
x=From(x),Del[x]++;
while(1){
int y=From(Slink[x]);
if(Sz[x]-Del[x]<K){
Res-=Mxlen[x]-Mxlen[Slink[x]];
Del[y]+=Del[x];
Fa[x]=y;
x=y;
}
else break;
}
}
void Build(char *S,int len){
int p=tot=1;
REP(i,0,len)Pos[i]=p=Extend(p,S[i]-'a');
int l,r;l=r=0;
REP(i,1,tot+1)Deg[Slink[i]]++,Fa[i]=i;
REP(i,1,tot+1)if(!Deg[i])Q[r++]=i;
while(l<r){
int A=Q[l++],F=Slink[A];
Sz[F]+=Sz[A];
if(Sz[A]>=K)Res+=Mxlen[A]-Mxlen[F];
else Fa[A]=F;
if(!(--Deg[F]))Q[r++]=F;
}
}
}SAM;
int main(){
while(scanf("%d%d%d",&n,&m,&K)!=EOF){
SAM.Clear();
Res=0;
scanf("%s",S);
REP(i,0,m){
scanf("%d",&Op[i]);
if(Op[i]==1)scanf("%s",S+n),n++;
}
SAM.Build(S,n);
DREP(i,m-1,-1)if(Op[i]==1)SAM.Delete(Pos[--n]);
else Ans[i]=Res;
REP(i,0,m)if(Op[i]==2)printf("%lld\n",Ans[i]);
}
return 0;
}
Problem K
求字典序第K大的子串,强制在线。
SA+二分其实挺好写的。。
SAM:
首先将反转串建SAM,那么此时的Slink树为后缀树,
每一个节点就对应了连续的一段区间的子串。
用最后一个字符确定一下节点顺序,二分求答案在哪个节点里即可。
Code
#include<bits/stdc++.h>
using namespace std;
#define Death Komachi
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define M 100004
#define LL long long
static const int N=M<<1;
int n;
char S[M];
vector<int>E[N];
struct SAM{
int Trans[N][26],Slink[N],Mxlen[N],R[N],tot;
void Clear(){
REP(i,1,tot+1)memset(Trans[i],R[i]=0,sizeof(Trans[i])),E[i].clear();
}
int Extend(int v,int c){
int z=++tot;
Mxlen[z]=Mxlen[v]+1;
R[z]=R[v]+1;
while(v && !Trans[v][c])Trans[v][c]=z,v=Slink[v];
if(!v)Slink[z]=1;
else {
int x=Trans[v][c];
if(Mxlen[x]==Mxlen[v]+1)Slink[z]=x;
else {
int y=++tot;
Slink[y]=Slink[x];
Mxlen[y]=Mxlen[v]+1;
memcpy(Trans[y],Trans[x],sizeof(Trans[x]));
while(Trans[v][c]==x)Trans[v][c]=y,v=Slink[v];
Slink[x]=Slink[z]=y;
}
}
return z;
}
void DFS(int A){
int B;
REP(i,0,E[A].size()){
DFS(B=E[A][i]);
R[A]=max(R[A],R[B]);
}
}
void Build(char *S,int len){
int p=tot=1;
REP(i,0,len)p=Extend(p,S[i]-'a');
REP(i,1,tot+1)E[Slink[i]].push_back(i);
DFS(1);
}
inline int Pos(int x){
return R[x]-Mxlen[Slink[x]]-1;
}
}SAM;
LL L[N],Mxs;
int D[N];
bool Cmp1(int a,int b){return L[a]<L[b];}
bool Cmp2(int a,int b){return S[SAM.Pos(a)]<S[SAM.Pos(b)];}
void DFS(int A){
Mxs+=SAM.Mxlen[A]-SAM.Mxlen[SAM.Slink[A]];
L[A]=Mxs;
sort(E[A].begin(),E[A].end(),Cmp2);
REP(i,0,E[A].size())DFS(E[A][i]);
}
void Build(){
Mxs=0;
DFS(1);
REP(i,1,SAM.tot+1)D[i]=i;
sort(D+1,D+SAM.tot+1,Cmp1);
// REP(i,1,SAM.tot+1)cerr<<D[i]<<' ';cerr<<endl;
// REP(i,1,SAM.tot+1)cerr<<SAM.R[D[i]]<<' ';cerr<<endl;
// REP(i,1,SAM.tot+1)cerr<<L[D[i]]<<' ';cerr<<endl;
}
void Query(int &l,int &r,LL k){
if(k>Mxs)l=r=0;
else {
L[0]=k;
int p=lower_bound(D+1,D+SAM.tot+1,0,Cmp1)-D;
l=SAM.R[D[p]],r=l-k+L[D[p-1]]-SAM.Mxlen[SAM.Slink[D[p]]]+1;
l=n-l+1,r=n-r+1;
}
printf("%d %d\n",l,r);
}
int main(){
while(scanf("%s",S)!=EOF){
n=strlen(S);
reverse(S,S+n);
SAM.Clear();
SAM.Build(S,n);
Build();
int q,l,r;
l=r=0;
LL k;
scanf("%d",&q);
while(q--){
scanf("%lld",&k);
k=(k^l^r)+1;
Query(l,r,k);
}
}
return 0;
}
Problem M
每次加入一个字符,
相当于将一条链上的点i的权值加上Mxlen[i]-Mxlen[Slink[i]]。
用LCT维护权值,然后查询每一个差值,差值即插入点到根的权值和。
对新建y的地方要特殊处理,比较难调。
Code
#include<bits/stdc++.h>
using namespace std;
#define Death Komachi
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define M 100004
#define LL long long
#define Mod 1000000007
static const int N=M<<1;
int n;
char S[M];
struct LCT{
int Fa[N],Ch[N][2],Val[N],Sum[N],Add[N],Num[N],Res[N];
void UpAdd(int x,int t){
if(!x)return;
Res[x]=(Res[x]+1ll*t*Sum[x])%Mod;
Num[x]=(Num[x]+t)%Mod;
Add[x]=(Add[x]+t)%Mod;
}
void Up(int x){
Sum[x]=(0ll+Sum[Ch[x][0]]+Sum[Ch[x][1]]+Val[x])%Mod;
Res[x]=(Res[Ch[x][0]]+Res[Ch[x][1]]+1ll*Num[x]*Val[x])%Mod;
}
void Down(int x){
if(Add[x]){
UpAdd(Ch[x][0],Add[x]);
UpAdd(Ch[x][1],Add[x]);
Add[x]=0;
}
}
#define isroot(x) (Ch[Fa[x]][0]!=x && Ch[Fa[x]][1]!=x)
void Rotate(int x){
int y=Fa[x],z=Fa[y],R=Ch[y][0]==x,&d=Ch[x][R];
Ch[y][!R]=d;
if(d)Fa[d]=y;
if(!isroot(y))Ch[z][Ch[z][1]==y]=x;
d=y,Fa[x]=z,Fa[y]=x;
Up(y);
}
int Stk[N];
void Splay(int x){
int top=0;
for(int t=x;!isroot(t);t=Fa[t])
Stk[++top]=Fa[t];
while(top)Down(Stk[top--]);
Down(x);
while(!isroot(x)){
int y=Fa[x];
if(!isroot(y))Rotate((Ch[Fa[y]][0]==y)!=(Ch[y][0]==x)?x:y);
Rotate(x);
}
Up(x);
}
void Access(int x){
for(int t=0;x;t=x,x=Fa[x])
Splay(x),Ch[x][1]=t,Up(x);
}
void Change(int x,int v,int t){
Val[x]=v,Num[x]=t,Up(x);
}
void Updata(int x,int t){
Access(x),Splay(x),UpAdd(Ch[x][0],t);
}
int Query(int x){
Access(x),Splay(x);
return Res[x];
}
void Link(int x,int y){
Fa[x]=y;
}
void Cut(int x,int y){
Access(x),Access(y),Fa[x]=0;
}
}LCT;
struct SAM{
int Trans[N][26],Slink[N],Mxlen[N],tot,Ans,Last;
int Extend(int v,int c){
int z=++tot;
Mxlen[z]=Mxlen[v]+1;
while(v && !Trans[v][c])Trans[v][c]=z,v=Slink[v];
if(!v)Slink[z]=1;
else {
int x=Trans[v][c];
if(Mxlen[x]==Mxlen[v]+1)Slink[z]=x;
else {
int y=++tot;
Slink[y]=Slink[x];
Mxlen[y]=Mxlen[v]+1;
memcpy(Trans[y],Trans[x],sizeof(Trans[x]));
while(Trans[v][c]==x)Trans[v][c]=y,v=Slink[v];
LCT.Cut(x,Slink[x]);
Slink[x]=Slink[z]=y;
LCT.Change(y,Mxlen[y]-Mxlen[Slink[y]],LCT.Num[x]);
LCT.Change(x,Mxlen[x]-Mxlen[y],LCT.Num[x]);
LCT.Link(y,Slink[y]);
LCT.Link(x,y);
}
}
LCT.Change(z,Mxlen[z]-Mxlen[Slink[z]],0);
LCT.Link(z,Slink[z]);
LCT.Updata(z,1);
int Tmp=((2ll*Ans-Last+LCT.Query(z))%Mod+Mod)%Mod;
Last=Ans,Ans=Tmp;
printf("%d\n",Ans);
return z;
}
void Build(char *S,int len){
int Ans=0,p=tot=1;
REP(i,0,len)p=Extend(p,S[i]-'a');
}
}SAM;
int main(){
scanf("%d%s",&n,S);
SAM.Build(S,n);
return 0;
}