字符串这一方面的算法。
概述:在Trie树上使用KMP算法得到Fail数组,使用Fail数组建树,
那么每次查询的串就是当前位置到Fail树的根得到查询,类似于一个后缀。
利用这个性质以及树上的各种算法可以做某些方面的题。
清空这一方面,一次性全部清空或动态清空均可,考虑到可能会写内存池,采用后者。
在Fail树上可以对当前点的信息或是回答进行记录,部分题目可以均摊复杂度。
https://vjudge.net/contest/202341#overview
T1 WP
类似于模板,分类后使用一个函数来完成某个方向上的答案收集。
可能是看代码长度的题目。
Code
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define Komachi is retarded
#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 1004
#define N 500004
int n,m,q;
char Map[M][M],C[M];
int Rx[]={-1,-1,0,1,1,1,0,-1},Ry[]={0,1,1,1,0,-1,-1,-1};
struct Node{
int x,y;
char c;
void Pri(int d){
printf("%d %d %c\n",x-Rx[c-'A']*d,y-Ry[c-'A']*d,c);
}
};
int Pos[M];
struct Trie{
int Next[N][26],Fail[N],Dep[N],sz;
Node Ans[N];
bool Mark[N];
void Add(int x){
scanf("%s",C);
int p=0;
REP(i,0,strlen(C)){
int &nt=Next[p][C[i]-'A'];
if(!nt)nt=++sz,Dep[nt]=Dep[p]+1;
p=nt;
}
Pos[x]=p;
}
int Q[N];
void ReBuild(){
int l,r;l=r=0;
REP(i,0,26)if(Next[0][i])Q[r++]=Next[0][i];
while(l<r){
int A=Q[l++];
REP(i,0,26){
int &p=Next[A][i];
if(p)Fail[Q[r++]=p]=Next[Fail[A]][i];
else p=Next[Fail[A]][i];
}
}
}
void Answer(int p,Node ans){
while(p){
if(Mark[p])return;
Ans[p]=ans;
Mark[p]=1;
p=Fail[p];
}
}
void Answer(int x,int y,int Rx,int Ry,char c){
int p=0;
while(x>=0 && y>=0 && x<n && y<m){
int nt=Next[p][Map[x][y]-'A'];
p=nt?nt:Fail[p];
Answer(p,(Node){x,y,c});
x+=Rx,y+=Ry;
}
}
}Trie;
int main(){
scanf("%d%d%d",&n,&m,&q);
REP(i,0,n)scanf("%s",Map[i]);
Trie.Dep[0]=-1;
REP(i,0,q)Trie.Add(i);
Trie.ReBuild();
REP(i,0,n){
REP(g,0,8)Trie.Answer(i,0,Rx[g],Ry[g],g+'A');
REP(g,0,8)Trie.Answer(i,m-1,Rx[g],Ry[g],g+'A');
}
REP(i,0,m){
REP(g,0,8)Trie.Answer(0,i,Rx[g],Ry[g],g+'A');
REP(g,0,8)Trie.Answer(n-1,i,Rx[g],Ry[g],g+'A');
}
REP(i,0,q){
int p=Pos[i];
Trie.Ans[Pos[i]].Pri(Trie.Dep[Pos[i]]);
}
return 0;
}
T2 RA
比较有意思的一道题。
考虑到n的范围小,如果已知两两之间串的拼接代价,可以利用状态压缩DP来求解。
所以用AC自动机将所有串加入,得出无法被访问的节点并标记。
从某个可行串的结尾点出发BFS求解到其他串结尾点的最小距离。
然后回到上一个问题去计算。
Code
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define M 60004
#define K 1024
#define chkmin(a,b) a=min(a,b)
int n,m,Pos[14],Len[14];
char C[M];
struct AC_Auto{
int Next[M][2],Fail[M],sz;
bool Ban[M];
void Clear(){memset(Next[0],sz=0,sizeof(Next[0]));}
int NewNode(){sz++;memset(Next[sz],Ban[sz]=Fail[sz]=0,sizeof(Next[sz]));return sz;}
void Add(int x,bool k){
scanf("%s",C);
int p=0;
REP(i,0,strlen(C)){
int &nt=Next[p][C[i]-'0'];
if(!nt)nt=NewNode();
p=nt;
}
if(k)Pos[x]=p,Len[x]=strlen(C);
else Ban[p]=1;
}
int Q[M];
void ReBuild(){
int l,r;l=r=0;
REP(i,0,2)if(Next[0][i])Q[r++]=Next[0][i];
while(l<r){
int A=Q[l++];
REP(i,0,2){
int &p=Next[A][i];
if(p)Fail[Q[r++]=p]=Next[Fail[A]][i];
else p=Next[Fail[A]][i];
}
Ban[A]|=Ban[Fail[A]];
}
}
int Lenth[14][14],Dis[M];
void BFS(int s){
int l,r;l=r=0;Q[r++]=s;
memset(Dis,63,sizeof(Dis));
Dis[s]=0;
while(l<r){
int A=Q[l++],B;
REP(i,0,2)if(Dis[B=Next[A][i]]==0x3f3f3f3f && !Ban[B])
Q[r++]=B,Dis[B]=Dis[A]+1;
}
}
int DP[14][1044],To[14][14];
void Answer(){
memset(To,63,sizeof(To));
REP(i,0,n){
BFS(Pos[i]);
REP(j,0,n)To[i][j]=Dis[Pos[j]];
}
memset(DP,63,sizeof(DP));
REP(i,0,n)DP[i][1<<i]=Len[i];
REP(i,1,1<<n)REP(j,0,n)if(DP[j][i]!=0x3f3f3f3f){
REP(k,0,n)chkmin(DP[k][i|(1<<k)],DP[j][i]+To[j][k]);
}
int Ans=0x3f3f3f3f;
REP(i,0,n)Ans=min(Ans,DP[i][(1<<n)-1]);
printf("%d\n",Ans);
}
}AC;
int main(){
while(scanf("%d%d",&n,&m)!=EOF){
if(!n && !m)break;
AC.Clear();
REP(i,0,n)AC.Add(i,1);
REP(i,0,m)AC.Add(i,0);
AC.ReBuild();
AC.Answer();
}
return 0;
}
T3 HABT
后缀和前缀的最大匹配长度,
转换到AC自动机上是A串结尾点在Fail树上到根的节点中包含的B串的最长长度。
离线询问,DFS,采用直接修改,记录旧值的方法得到当前栈内的点的答案。
复杂度为
O(n)
,常数可能较大。
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define Komachi is retarded
#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 LREP(i,A,E) for(int i=E.Head[A];i;i=E.Next[i])
#define M 100044
inline void chkmax(int &a,const int &b){if(a<b)a=b;}
int n,m,len,L[M],R[M],Nm[444],Len[M],Fr[M],Pos[M];
char C[M];
template<const int Len,const int Num>struct Linklist{
int Next[Len],Head[Num],Val[Len],tot;
int operator [](int x){return Val[x];}
void pb(int u,int v){Next[++tot]=Head[u],Val[Head[u]=tot]=v;}
void Clear(){memset(Head,tot=0,sizeof(Head));}
};
Linklist<M,M>E,AD,Qes,Rev;
int Tmp[M],B[M],Ans[M],Mx[M];
struct AC_Auto{
int Next[M][4],Fail[M],sz;
void Clear(){
REP(i,0,sz+1)memset(Next[i],Fail[i]=0,sizeof(Next[i]));
sz=0;
}
void Add(int x,int L,int R){
int p=0,lt=0;
REP(i,L,R){
int &nt=Next[p][Nm[C[i]]];
if(!nt)nt=++sz;
Len[i]=++lt;
AD.pb(p=nt,i);
Fr[i]=x;
}
Pos[x]=p;
}
int Q[M];
void ReBuild(){
int l,r;l=r=0;
REP(i,0,4)if(Next[0][i])Q[r++]=Next[0][i];
while(l<r){
int A=Q[l++];
REP(i,0,4){
int &p=Next[A][i];
if(p)Fail[Q[r++]=p]=Next[Fail[A]][i];
else p=Next[Fail[A]][i];
}
E.pb(Fail[A],A);
}
}
}AC;
void Answer(int A){
LREP(i,A,AD){
int j=AD[i];
Tmp[j]=Mx[Fr[j]];
chkmax(Mx[Fr[j]],Len[j]);
Rev.pb(A,j);
}
LREP(i,A,Qes){
int j=Qes[i];
Ans[j]=Mx[B[j]];
}
LREP(i,A,E)
Answer(E[i]);
LREP(i,A,Rev){
int j=Rev[i];
Mx[Fr[j]]=Tmp[j];
}
}
int main(){
Nm['A']=0;
Nm['G']=1;
Nm['C']=2;
Nm['T']=3;
while(scanf("%d%d",&n,&m)!=EOF){
memset(Mx,0,sizeof(Mx));
AC.Clear();
AD.Clear();
E.Clear();
Qes.Clear();
Rev.Clear();
len=0;
REP(i,0,n){
L[i]=len;
scanf("%s",C+len);
len+=strlen(C+len);
R[i]=len;
AC.Add(i,L[i],R[i]);
}
REP(i,0,m){
int a;
scanf("%d%d",&a,&B[i]);
a--,B[i]--;
Qes.pb(Pos[a],i);
}
AC.ReBuild();
Answer(0);
REP(i,0,m)printf("%d\n",Ans[i]);
}
return 0;
}
T4 GREW
在Fail树上DP,线段树优化查询。
Code
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define Komachi is retarded
#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 LREP(i,A,E) for(int i=E.Head[A];i;i=E.Next[i])
#define M 300004
inline void chkmax(int &a,const int &b){if(a<b)a=b;}
int T,n,m,len,L[M],R[M],W[M],Pos[M],Ans,DFN[M],Ed[M],dfn;
char C[M];
template<const int Len,const int Num>struct Linklist{
int Next[Len],Head[Num],Val[Len],tot;
int operator [](int x){return Val[x];}
void pb(int u,int v){Next[++tot]=Head[u],Val[Head[u]=tot]=v;}
void Clear(){memset(Head,tot=0,sizeof(Head));}
};
Linklist<M,M>E;
struct Segtree{
#define lp (p<<1)
#define rp (p<<1|1)
#define lson l,mid,lp
#define rson mid+1,r,rp
int Mx[M<<2];
void Clear(){memset(Mx,0,sizeof(Mx));}
void Updata(int l,int r,int p,int a,int b,int t){
if(l>b || r<a)return;
if(a<=l&&r<=b){chkmax(Mx[p],t);return;}
int mid=l+r>>1;
Updata(lson,a,b,t);
Updata(rson,a,b,t);
}
int Query(int l,int r,int p,int a,int b){
if(l>b || r<a)return 0;
if(a<=l&&r<=b)return Mx[p];
int mid=l+r>>1;
return max(Mx[p],max(Query(lson,a,b),Query(rson,a,b)));
}
}Tree;
struct AC_Auto{
int Next[M][26],Fail[M],sz;
void Clear(){
REP(i,0,sz+1)memset(Next[i],Fail[i]=0,sizeof(Next[i]));
sz=0;
}
void Add(int x,int L,int R){
int p=0;
REP(i,L,R){
int &nt=Next[p][C[i]-'a'];
if(!nt)nt=++sz;
p=nt;
}
Pos[x]=p;
}
int Q[M];
void ReBuild(){
int l,r;l=r=0;
REP(i,0,26)if(Next[0][i])Q[r++]=Next[0][i];
while(l<r){
int A=Q[l++];
REP(i,0,26){
int &p=Next[A][i];
if(p)Fail[Q[r++]=p]=Next[Fail[A]][i];
else p=Next[Fail[A]][i];
}
E.pb(Fail[A],A);
}
}
}AC;
void DFS(int A){
DFN[A]=++dfn;
LREP(i,A,E)DFS(E[i]);
Ed[A]=dfn;
}
int main(){
scanf("%d",&T);
REP(Case,1,T+1){
Ans=dfn=len=0;
AC.Clear();
E.Clear();
Tree.Clear();
scanf("%d",&n);
REP(i,0,n){
L[i]=len;
scanf("%s",C+len);
len+=strlen(C+len);
R[i]=len;
AC.Add(i,L[i],R[i]);
scanf("%d",&W[i]);
}
AC.ReBuild();
DFS(0);
REP(i,0,n){
int A=Pos[i];
int DP=0,p=0;
REP(j,L[i],R[i]){
p=AC.Next[p][C[j]-'a'];
chkmax(DP,Tree.Query(1,AC.sz+1,1,DFN[p],DFN[p]));
}
Tree.Updata(1,AC.sz+1,1,DFN[A],Ed[A],DP+W[i]);
chkmax(Ans,DP+W[i]);
}
printf("Case #%d: %d\n",Case,Ans);
}
return 0;
}
T5 S
按照上面的方法来的话写起来比较诡异。
查询前缀+后缀,即在后缀的结尾点上在Fail树上的子树那些点有该前缀的点数。
Hash去重,在Fail树上用Dsu维护Trie树可以写出来。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<map>
using namespace std;
#define Komachi is retarded
#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 LREP(i,A,E) for(int i=E.Head[A];i;i=E.Next[i])
#define M 500044
#define ULL unsigned long long
#define Base 233
map<ULL,int>Mark;
ULL Bc[M],H1[M],H2[M];
ULL Hash(ULL *H,int l,int r){
return H[r]-H[l-1]*Bc[r-l+1];
}
int T,n,m,len,Beg[M],Pr[M],Ans[M];
char C[M],Prx[M],Sux[M];
template<const int Len,const int Num>struct Linklist{
int Next[Len],Head[Num],Val[Len],tot;
int operator [](int x){return Val[x];}
void pb(int u,int v){Next[++tot]=Head[u],Val[Head[u]=tot]=v;}
void Clear(){memset(Head,tot=0,sizeof(Head));}
};
Linklist<M,M>E,D,Qes;
struct AC_Auto{
int Next[M<<1][26],Fail[M<<1],sz;
int Cnt[M<<1];
void Clear(){memset(Next[0],sz=0,sizeof(Next[0]));}
int NewNode(){
sz++;
memset(Next[sz],Fail[sz]=Cnt[sz]=0,sizeof(Next[sz]));
return sz;
}
int Add(int x){
int p=0;
REP(i,Beg[x],Beg[x+1]){
int &nt=Next[p][C[i]-'a'];
if(!nt)nt=NewNode();
Cnt[p=nt]++;
}
return p;
}
int Q[M<<1];
void ReBuild(){
int l,r;l=r=0;
REP(i,0,26)if(Next[0][i])Q[r++]=Next[0][i];
while(l<r){
int A=Q[l++];
REP(i,0,26){
int &p=Next[A][i];
if(p)Fail[Q[r++]=p]=Next[Fail[A]][i];
else p=Next[Fail[A]][i];
}
E.pb(Fail[A],A);
}
}
void Query(int x){
int p=0;
REP(i,0,strlen(Sux)){
int &nt=Next[p][Sux[i]-'a'];
if(!nt)nt=NewNode();
p=nt;
}
Qes.pb(p,x);
}
int QueryC(int x){
int p=0;
REP(i,Pr[x],Pr[x+1]){
int nt=Next[p][Prx[i]-'a'];
if(!nt)return 0;
p=nt;
}
return Cnt[p];
}
}AC,Trie;
int Sz[M],Son[M];
void Get(int A){
int B;
Sz[A]=0;
LREP(i,A,D)Sz[A]++;
Son[A]=M-1;
LREP(i,A,E){
Get(B=E[i]);
Sz[A]+=Sz[B];
if(Sz[B]>Sz[Son[A]])Son[A]=B;
}
}
void Add(int A){
LREP(i,A,D)Trie.Add(D[i]);
LREP(i,A,E)Add(E[i]);
}
void Answer(int A){
int B;
LREP(i,A,E)if((B=E[i])!=Son[A])
Answer(B),Trie.Clear();
if(Son[A]!=M-1)Answer(Son[A]);
LREP(i,A,E)if((B=E[i])!=Son[A])Add(B);
LREP(i,A,D)Trie.Add(D[i]);
LREP(i,A,Qes){
int j=Qes[i];
Ans[j]+=Trie.QueryC(j);
}
}
int main(){
Bc[0]=1;
REP(i,1,M)Bc[i]=Bc[i-1]*Base;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
len=0;
AC.Clear();
E.Clear();
D.Clear();
Qes.Clear();
Mark.clear();
memset(Ans,0,sizeof(Ans));
REP(i,0,n){
scanf("%s",C+len);
len+=strlen(C+len);
Beg[i+1]=len;
D.pb(AC.Add(i),i);
}
REP(i,0,n){
ULL Val=0;
REP(j,Beg[i],Beg[i+1])
Val=Val*Base+C[j];
Mark[Val]++;
}
len=0;
REP(i,0,m){
int a,b;
scanf("%s%s",Prx+len,Sux);
a=strlen(Prx+len);
b=strlen(Sux);
Pr[i+1]=len+a;
AC.Query(i);
REP(j,0,a)H1[j+1]=H1[j]*Base+Prx[len+j];
REP(j,0,b)H2[j+1]=H2[j]*Base+Sux[j];
REP(j,1,min(a,b)+1)if(Hash(H1,a-j+1,a)==Hash(H2,1,j))
Ans[i]-=Mark[Hash(H1,1,a-j)*Bc[b]+H2[b]];
len+=a;
}
AC.ReBuild();
Trie.Clear();
Get(0);
Answer(0);
REP(i,0,m) printf("%d\n",Ans[i]);
}
return 0;
}
另外有一种比较巧妙的方法。
将查询在前缀和后缀中插入一个特殊字符并拼接来作为模式串。
将所有原来的串用 S+特殊字符+S 拼接来作为主串。
所有主串均进行一次匹配,结束后再Hash去重即可。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<map>
using namespace std;
#define Komachi is retarded
#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 LREP(i,A,E) for(int i=E.Head[A];i;i=E.Next[i])
#define M 604444
#define ULL unsigned long long
#define Base 233
map<ULL,int>Mark;
ULL Bc[M],H1[M],H2[M];
ULL Hash(ULL *H,int l,int r){
return H[r]-H[l-1]*Bc[r-l+1];
}
int T,n,q,len,Beg[M],Ans[M],Pos[M];
char C[M<<1],Prx[M<<1],Sux[M];
template<const int Len,const int Num>struct Linklist{
int Next[Len],Head[Num],Val[Len],tot;
int operator [](int x){return Val[x];}
void pb(int u,int v){Next[++tot]=Head[u],Val[Head[u]=tot]=v;}
void Clear(){memset(Head,tot=0,sizeof(Head));}
};
Linklist<M,M>E;
struct AC_Auto{
int Next[M][27],Fail[M],sz;
int Cnt[M];
void Clear(){memset(Next[0],sz=0,sizeof(Next[0]));}
int NewNode(){
sz++;
memset(Next[sz],Cnt[sz]=Fail[sz]=0,sizeof(Next[sz]));
return sz;
}
void Add(int x,int l){
int p=0;
REP(i,0,l){
int &nt=Next[p][Sux[i]-'a'];
if(!nt)nt=NewNode();
p=nt;
}
Pos[x]=p;
}
int Q[M];
void ReBuild(){
int l,r;l=r=0;
REP(i,0,27)if(Next[0][i])Q[r++]=Next[0][i];
while(l<r){
int A=Q[l++];
REP(i,0,27){
int &p=Next[A][i];
if(p)Fail[Q[r++]=p]=Next[Fail[A]][i];
else p=Next[Fail[A]][i];
}
E.pb(Fail[A],A);
}
}
void Answer(int x){
int p=0;
REP(i,Beg[x],Beg[x+1])Cnt[p=Next[p][C[i]-'a']]++;
}
void DFS(int A){
LREP(i,A,E)DFS(E[i]),Cnt[A]+=Cnt[E[i]];
}
}AC;
int main(){
Bc[0]=1;
REP(i,1,M)Bc[i]=Bc[i-1]*Base;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&q);
Mark.clear();
memset(Ans,len=0,sizeof(Ans));
AC.Clear();
E.Clear();
REP(i,0,n){
scanf("%s",C+len);
int m=strlen(C+len);
len+=m;
C[len++]=26+'a';
ULL Val=0;
REP(j,0,m)Val=Val*Base+(C[len++]=C[Beg[i]+j]);
Beg[i+1]=len;
Mark[Val]++;
}
REP(i,0,q){
scanf("%s%s",Prx,Sux);
int a=strlen(Prx),b=strlen(Sux);
REP(j,0,a)H1[j+1]=H1[j]*Base+Prx[j];
REP(j,0,b)H2[j+1]=H2[j]*Base+Sux[j];
REP(j,1,min(a,b)+1)if(Hash(H1,a-j+1,a)==Hash(H2,1,j))
Ans[i]-=Mark[Hash(H1,1,a-j)*Bc[b]+H2[b]];
Sux[b]=26+'a';
REP(j,0,a)Sux[b+j+1]=Prx[j];
AC.Add(i,a+b+1);
}
AC.ReBuild();
REP(i,0,n)AC.Answer(i);
AC.DFS(0);
REP(i,0,q)printf("%d\n",Ans[i]+AC.Cnt[Pos[i]]);
}
return 0;
}