• 题解 CF163E e-Government


    (Large atural) CF163E e-Government 原题链接

    解法

    这种题算是比较套路的了,我们看见这种多字符串匹配,会想到AC自动机

    如果不会AC自动机的可以看看 我的博客 ,希望能给您带来帮助。

    那么如果我们对于所有字典树节点连边 (i-fail_i)(根节点没有 (fail),不连边),那么就得到了一棵 Fail树

    那么,如果一号点为根节点,那所有 (fail) 直接或间接指向 (i) 号点的节点,都在 (i) 的子树中。

    所以查询字符串 (X) 在字符串 (Y) 中出现几次,等价于建出 TrieFail树 后,在 Fail树中 以 “(X)的结束节点”(设为 (i)) 为根的子树中有多少个 (Y) 包含的节点。

    不理解可以看这解释:比如 (j) 号点是 (Y) 所包含的,是 (Y) 的第 (id) 个节点,那么代表在 (Y) 查询时,(j)(fail) 可以跑出 (X),所以 (X)(Y)(1sim id) 这个子串的后缀。

    先想想只有询问怎么做:

    设询问的字符串为 (T),则显然有这样一个方法:让 (T)Trie 上跑,经过的点对于的 Fail树节点权值加一。设一个字符串在 Trie 的结尾节点为 (G),然后我们查询集合 (S) 中每个字符串的 (G) ,对应的 Fail树节点 ,的子树和(可能有点绕)。最后我们再把那些加一的点再减一,复原原来的树。

    然而这样时间显然会炸。

    但是,给 (T) 的路径赋值,然后查询每个 (G) 的子树权值 等价于(G) 的子树赋值,然后查询 (T) 的路径权值。

    所以我们一开始给每个 (G) 的子树加一,询问时 (T)Trie 上跑,每跑到一个节点,就加上这个节点对应的 Fail树 节点的权值。这个操作的复杂度是 (O(|T|)) 的。

    这样修改操作也迎刃而解,增加字符串 (A) 就给 (G_A) 子树加一,删除就减一。

    最后一个问题:怎么样给一个节点的子树加减?只需要在树的 DFS序 上操作即可。因为一个点的子树在 DFS序 上是连续的,所以我们只用区间加值(给子树加减)、单点询问( (T) 在 Trie 跑时查询)就行了,显然套分块模板即可。

    代码

    #include<bits/stdc++.h>
    #define rep(i,x,y) for(int i=x;i<=y;++i)
    #define mar(o) for(int E=fst[o];E;E=e[E].nxt)
    #define v e[E].to
    #define kwei(x) ((x-1)/B+1)
    #define kl(x) ((x-1)*B+1)
    #define kr(x) (x*B)
    using namespace std;
    const int n7=1012345;
    struct dino{int to,nxt;}e[n7];
    int n,T,B,ecnt,fst[n7],a[n7],blo[n7],cnt=1,tre[n7][26],fail[n7];
    int ned[n7],head,tail,que[n7],t,L[n7],R[n7];
    char cr[n7];bool exi[n7];
    
    int rd(){
       int shu=0;char ch=getchar();
       while(!isdigit(ch))ch=getchar();
       while(isdigit(ch))shu=(shu<<1)+(shu<<3)+(ch^48),ch=getchar();
       return shu;
    }
    
    void edge(int sta,int edn){
    	ecnt++;
    	e[ecnt]=(dino){edn,fst[sta]};
    	fst[sta]=ecnt;
    }
    
    void insert(int z){
    	int len=strlen(cr+1),now=1;
    	rep(i,1,len){
    		int ch=cr[i]-'a';
    		if(!tre[now][ch]){
    			cnt++,tre[now][ch]=cnt;
    		}
    		now=tre[now][ch];
    	}
    	ned[z]=now;
    }
    
    void Gfail(){
    	head=1,tail=1,que[1]=1;
    	rep(i,0,25)tre[0][i]=1;
    	while(head<=tail){
    		int now=que[head];
    		rep(i,0,25){
    			int edn=tre[ fail[now] ][i];
    			if(tre[now][i]){
    				fail[ tre[now][i] ]=edn;
    				edge(edn,tre[now][i]);
    				tail++,que[tail]=tre[now][i];
    			}
    			else tre[now][i]=edn;
    		}
    		head++;
    	}
    }
    
    void dfs(int o){
    	t++,L[o]=t;
    	mar(o)dfs(v);
    	R[o]=t;
    }
    
    void updat(int l,int r,int val){
    	if( kwei(l)==kwei(r) ){
    		rep(i,l,r)a[i]+=val;
    		return;
    	}
    	rep(i,l,kr( kwei(l) ))a[i]+=val;
    	rep(i,kl( kwei(r) ),r)a[i]+=val;
    	rep(i,kwei(l)+1,kwei(r)-1)blo[i]+=val;
    }
    
    int Tquery(int z){
    	return a[z]+blo[ kwei(z) ];
    }
    
    int query(){
    	int len=strlen(cr+1),now=1,tot=0;
    	rep(i,1,len){
    		now=tre[now][ cr[i]-'a' ];
    		tot+=Tquery( L[now] );
    	} 
    	return tot;
    }
    
    int main(){
    	T=rd(),n=rd();
    	rep(i,1,n)scanf("%s",cr+1),insert(i);
    	Gfail(),dfs(1);
    	B=sqrt(cnt*2);//事实证明,块长这样设会玄学地更快,直接卡到最优解1st
    	rep(i,1,n)exi[ ned[i] ]=1,updat(L[ ned[i] ],R[ ned[i] ],1);
    	while(T--){
    		char sys=getchar();
    		while(sys!='-'&&sys!='+'&&sys!='?')sys=getchar();
    		if(sys=='+'){
    			int z=ned[ rd() ];
    			if(!exi[z])updat(L[z],R[z],1),exi[z]=1;
    		}
    		if(sys=='-'){
    			int z=ned[ rd() ];
    			if(exi[z])updat(L[z],R[z],-1),exi[z]=0;
    		}
    		if(sys=='?'){
    			scanf("%s",cr+1);
    			printf("%d
    ",query());
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    让程序的性能提升10倍
    以正确的姿势实现一棵JavaScript菜单树
    利用php数组函数进行函数式编程
    yum安装下的nginx,如何添加模块,和添加第三方模块
    前端面试题 vue
    前端常用面试题目及答案-HTML&CSS篇
    使用after伪类,配合IE的zoom或者overflow清除浮动
    函数式编程
    在CentOS 7上安装Node.js
    node中__dirname、__filename、process.cwd()、process.chdir()表示的路径
  • 原文地址:https://www.cnblogs.com/BlankAo/p/14375198.html
Copyright © 2020-2023  润新知