1. 程式人生 > 實用技巧 >[題解] lg P2336 [SCOI2012]喵星球上的點名 --ac自動機

[題解] lg P2336 [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\n",j);
		}
		//printf("hello\n");
	}
	//printf("what!\n");
	for(int i=1;i<=m;i++){
		int qid=ACA.queryed[i];
		printf("%d\n",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("\n");
}
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\n");
	solve1();
	solve2();
	return 0;
}

參考資料

Lskkkno1學長的好部落格