1. 程式人生 > 實用技巧 >「ICPC World Finals 2019 何以伊名始

「ICPC World Finals 2019 何以伊名始

「ICPC World Finals 2019 何以伊名始

本題現已知四種做法,如果不會後綴系列結構可以直接看Solution4

設初始樹大小和查詢總長均為\(O(n)\)

Solution1

由於查詢只有1e6,因此出現的不同查詢串長度最多\(O(\sqrt {10^6})=1500\)

考慮對於每一種做一次dfs,在\(\text{Hash Table}\)中查詢,複雜度為\(O(n\sqrt n)\)

Solution2

將樹上節點字尾排序,然後每次插入需要詢問的字元就二分字尾區間

預處理複雜度為\(O(n\log n)\),查詢涉及二分和倍增,複雜度為\(O(n\log ^2 n)\)

Solution3

給定的樹看做trie樹,可以對於trie樹建廣義字尾自動機,然後倒著讓詢問串去匹配,一旦失配答案為0,

需要預處理\(link\)樹的子樹和,時間複雜度為\(O(n)\),空間複雜度為\(O(n|\Sigma|)\)

Solution4

將詢問的串倒著插入,構建AC自動機

由於AC自動機預處理,時間空間複雜度為\(O(n|\Sigma|)\)

然後考慮對於樹上每一個字首在AC自動機上匹配,每次從父親轉移過來,複雜度為\(O(n)\)

然後是常見的AC自動機操作,\(fail\)樹上的子樹累和即可

需要詢問離線,因此有一定侷限性

#include<bits/stdc++.h>
using namespace std;
#pragma GCC optimize(2)
#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)
char IO;
int rd(){
	int s=0;
	while(!isdigit(IO=getchar()));
	do s=(s<<1)+(s<<3)+(IO^'0');
	while(isdigit(IO=getchar()));
	return s;
}

const int N=1e6+10;

int n,m,len,F[N],A[N];
char s[N],t[N];
int nxt[N][26],fail[N],cnt,pos[N];
int Q[N],L=1,R;
void Build(){
	rep(i,0,25) if(nxt[0][i]) Q[++R]=nxt[0][i];
	while(L<=R) {
		int u=Q[L++];
		rep(i,0,25) {
			int &v=nxt[u][i];
			if(v) fail[v]=nxt[fail[u]][i],Q[++R]=v;
			else v=nxt[fail[u]][i];
		}
	}
}

int main(){
	n=rd(),m=rd();
	rep(i,1,n) scanf("%c",s+i),F[i]=rd();
	rep(i,1,m) {
		scanf("%s",t+1);
		int now=0;
		drep(j,strlen(t+1),1) {
			int c=t[j]-'A';
			if(!nxt[now][c]) nxt[now][c]=++cnt;
			now=nxt[now][c];
		}
		pos[i]=now;
	}
	Build();
	rep(i,1,n) A[F[i]=nxt[F[F[i]]][s[i]-'A']]++;
	drep(i,R,1) A[fail[Q[i]]]+=A[Q[i]];
	rep(i,1,m) printf("%d\n",A[pos[i]]);
}