zoukankan      html  css  js  c++  java
  • 题解 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学长的好博客

  • 相关阅读:
    EntityFramework 启用迁移 EnableMigrations 报异常 "No context type was found in the assembly"
    JAVA 访问FTP服务器示例(2)
    NuGet Package Manager 更新错误解决办法
    JAVA 访问FTP服务器示例(1)
    RemoteAttribute 的使用问题
    诡异的 javascript 变量
    javascript apply用法
    Babun 中文乱码
    GSM呼叫过程
    转站博客园
  • 原文地址:https://www.cnblogs.com/fpjo/p/13896050.html
Copyright © 2011-2022 走看看