「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]]);
}

posted @ 2020-09-18 21:44  chasedeath  阅读(140)  评论(0编辑  收藏  举报