题解 lg2336 [SCOI2012]喵星球上的点名 --ac自动机

由于我是看的题解写的,故一定要写这篇题解

题意

每只小猫有两个字符串.每次点名给一个字符串,如果点名的字符串中包含一只小猫的姓或名的子串,其必须答"到"

  1. 每次点名会有几只小猫答"到"

  2. 每只小猫会答几次"到"

思路

看到就有写AC自动机的冲动

这道题的思路有很多,什么 SA+莫队 , SA+树状数组,SAM,ACM乱搞等,不过我只会这篇题解讲的那种太菜了,就做一个进一步诠释吧

首先,姓和名根据题意是应该分开的,我们可以加一个不存在的字符作为分界线

第一次看题目我是想把名字串建AC自动机,点名串跑的,但这显然不行.(子串这个太毒瘤了)

正解是这样,把名字串和点名串都插进AC自动机中,然后构建trie树.trie树的性质是字符串(A)若能在(fail)树上跳到字符串(B),则(B)一定是(A)的后缀.当求出一个串所有前缀的所有后缀,就求出了这个串的所有子串.

然后跑树链剖分,求出trie树dfs序以及其它用以求lca的东东

接下来就是如何解决两个问题的作诠释环节

容易理解, 点名串 所能点到 名字串子串 都在trie树上其终止节点的子树中

第一问

对于一个名字串的每一个前缀(总前缀个数不超过字符串总长),覆盖它到根的路径(覆盖表示加多次算一次)。

对每一个名字串都这么做,看点名串总共被多少个名字串给覆盖。

树上链修改,单点查询的问题先转化成树上单点修改,子树查询的问题。

由于覆盖多次算只算一次,就要把覆盖多的部分减掉。

这里有一个小 trick。

对名字串的前缀按 dfs 序排序,减掉的部分就是每相邻节点的 lca。

这样就可以做覆盖多次算一次了。

将每个名字串的前缀在树状数组上加1,然后统计点名串终止节点在子树的和

不过每个点名串可能会有重复,就需要进行容斥.通过dfs序排序后在一个子树内的点会相邻,那么lca就可以消去影响了

第二问

对于一个名字串的所有一个前缀,看它们总共覆盖了多少点名串。

树上单点修改,链查询的问题先转化trie树上子树修改, 单点查询的问题。

同样利用上面的 trick,减掉 dfs 序相邻节点 lca 的贡献即可。

点名串所能点到名字串子串都在trie树上其的子树中,所以在树状数组上差分,进行区间修改,接着对于每一个名字串的前缀求前缀和,即可求出这一个名字串被几个点名串点到

重复的话同理

细节

这道题我还是收获挺多的

  1. map建AC自动机的技巧(getfail()),(其实感觉和普通的也没有太大区别)
  2. fail树上的操作
  3. 可以不重复代码的丫(指我的建AC自动机又臭又长)

代码

我一定会再打一遍的

/*
 * @Author: fpjo 
 * @Date: 2020-10-29 08:14:51 
 * @Last Modified by: fpjo
 * @Last Modified time: 2020-10-29 11:06:43
 */
#include<bits/stdc++.h>
using namespace std;
int const N=5e4+10,M=1e5+10,MAXN=(N+M)<<1;
map<int,int>::iterator it;
int n,m,_,tott;
int h[MAXN],hson[MAXN],siz[MAXN],dep[MAXN],top[MAXN],dfn[MAXN],fa[MAXN];
struct edge{
	int to,next;
}e[MAXN<<1];
void add(int u,int v){
	e[++_].to=v,e[_].next=h[u],h[u]=_;
}
inline int read(){
	int x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return f*x;
}
struct ACAutomaton{
	int tot;
	int nameed[MAXN],queryed[MAXN];
	struct point{
		map<int,int>Map;
		int id,cntme,cntd,fail,fa;
	}trie[MAXN];
	queue<int>q;
	void insertname(int id){
		int len1=read();int now=0;
		for(int i=1;i<=len1;i++){
			int a=read();
			if(!trie[now].Map.count(a)){
				trie[now].Map[a]=++tot;
				trie[tot].fa=now;
			}
			now=trie[now].Map[a];
		}
		if(!trie[now].Map.count(-1)){
			trie[now].Map[-1]=++tot;
			trie[tot].fa=now;
		}
		now=trie[now].Map[-1];
		int len2=read();
		for(int i=1;i<=len2;i++){
			int a=read();
			if(!trie[now].Map.count(a)){
				trie[now].Map[a]=++tot;
				trie[tot].fa=now;
			}
			now=trie[now].Map[a];
		}
		nameed[id]=now;
	}
	void insertquery(int id){
		int len=read(),now=0;
		for(int i=1;i<=len;i++){
			int a=read();
			if(!trie[now].Map.count(a)){
				trie[now].Map[a]=++tot;
				trie[tot].fa=now;
			}
			now=trie[now].Map[a];
		}
		queryed[id]=now;
	}
	int getfail(int f,int c){
		if(trie[f].Map.count(c))return trie[f].Map[c];
		else if(!f)return f;
		return trie[f].Map[c]=getfail(trie[f].fail,c);
	}
	void buildfail(){
		for(it = trie[0].Map.begin();it!=trie[0].Map.end();++it){
			trie[it->second].fail=0;
			q.push(it->second);
		}
		while(!q.empty()){
			int x=q.front();q.pop();
			for(it=trie[x].Map.begin();it!=trie[x].Map.end();++it){
				trie[it->second].fail=getfail(trie[x].fail,it->first);
				q.push(it->second);
			}
		}
		for(int i=1;i<=tot;i++)add(trie[i].fail,i);
	}
}ACA;
struct BIT{
	int tree[MAXN];
	#define lowbit(x) x&-x
	void clear(){memset(tree,0,sizeof(tree));}
	void insert(int x,int a){
		for(;x<=MAXN;x+=lowbit(x))tree[x]+=a;
	}
	inline int query(int x){
		int sum=0;
		for(;x;x-=lowbit(x))sum+=tree[x];
		return sum;
	}
	#undef lowbit
}Bit;
void dfs1(int x,int dad,int depth){
	fa[x]=dad;siz[x]=1;dep[x]=depth;
	int maxn=-1;
	for(int i=h[x];i;i=e[i].next){
		int to=e[i].to;
		if(to==dad)continue;
		dfs1(to,x,depth+1);
		siz[x]+=siz[to];
		if(maxn<siz[to])maxn=siz[to],hson[x]=to;
	}
}
void dfs2(int x,int topf){
	top[x]=topf;dfn[x]=++tott;
	if(!hson[x])return;
	dfs2(hson[x],topf);
	for(int i=h[x];i;i=e[i].next){
		int to=e[i].to;
		if(to==fa[x] || to==hson[x])continue;
		dfs2(to,to);
	}
}
int lca(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		x=fa[top[x]];
	}
	if(dep[x]<dep[y])return x;
	return y;
}
int arr[MAXN];
bool cmp(int a,int b){
	return dfn[a]<dfn[b];
}
void solve1(){
	Bit.clear();
	for(int i=1;i<=n;i++){
		int u=ACA.nameed[i],cnt=0;
		while(u){
			arr[++cnt]=u;
			Bit.insert(dfn[u],1);
			u=ACA.trie[u].fa;
		}
		sort(arr+1,arr+cnt+1,cmp);
		for(int j=1;j<cnt;j++){
			Bit.insert(dfn[lca(arr[j],arr[j+1])],-1);
			//printf("test: %d
",j);
		}
		//printf("hello
");
	}
	//printf("what!
");
	for(int i=1;i<=m;i++){
		int qid=ACA.queryed[i];
		printf("%d
",Bit.query(dfn[qid]+siz[qid]-1)-Bit.query(dfn[qid]-1));
	}
}
void solve2(){
	Bit.clear();
	for(int i=1;i<=m;i++){
		int u=ACA.queryed[i],cnt=0;
		Bit.insert(dfn[u],1);
		Bit.insert(dfn[u]+siz[u],-1);
	}
	for(int i=1;i<=n;i++){
		int u=ACA.nameed[i];int cnt=0,ans=0;
		while(u){
			arr[++cnt]=u;
			ans+=Bit.query(dfn[u]);
			u=ACA.trie[u].fa;
		}
		sort(arr+1,arr+cnt+1,cmp);
		for(int j=1;j<cnt;j++){
			ans-=Bit.query(dfn[lca(arr[j],arr[j+1])]);
		}
		printf("%d ",ans);
	}
	printf("
");
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)ACA.insertname(i);
	for(int i=1;i<=m;i++)ACA.insertquery(i);
	ACA.buildfail();
	dfs1(0,0,0);
	dfs2(0,0);
	//printf("hello
");
	solve1();
	solve2();
	return 0;
}

参考资料

Lskkkno1学长的好博客

原文地址:https://www.cnblogs.com/fpjo/p/13896050.html