• 题解 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学长的好博客

  • 相关阅读:
    #419 Div2 Problem B Karen and Coffee (统计区间重叠部分 && 前缀和)
    #418 Div2 Problem B An express train to reveries (构造 || 全排列序列特性)
    FZU 2203 单纵大法好 (二分 && 贪心)
    51Nod 1413 权势二进制 (思维)
    51Nod 1433 0和5 (数论 && 被9整除数的特点)
    POJ 2492 A Bug's Life (带权并查集 && 向量偏移)
    POJ 1456 Supermarket (贪心 && 并查集优化)
    POJ 1182 食物链 (带权并查集 && 向量偏移)
    #417 Div2 Problem B Sagheer, the Hausmeister (DFS && 枚举)
    #417 Div2 Problem C Sagheer and Nubian Market (二分 && std::accumulate)
  • 原文地址:https://www.cnblogs.com/fpjo/p/13896050.html
Copyright © 2020-2023  润新知