zoukankan      html  css  js  c++  java
  • P6177 Count on a tree II/【模板】树分块

    P6177 Count on a tree II/【模板】树分块

    树分块板题。

    树分块有很多种形式,可以按结点个数分块,深度分块...各有优劣。

    具体可以看这里

    这里用的是按结点个数分块。

    那么就可以这样来处理每一个询问:

    (u`)(u) 的块的根 ,(v')(v) 的根,(t1)(u) 的祖先中最接近 (lca) 的根,(t2)(v) 的祖先中最接近 (lca) 的根。

    于是我们的路径就可以划分成 (A=(u,u'))(B=(v,v`))(C=(u`,t1))(D=(v`,t2))(E=(t1,lca))(F=(t2,lca))

    对于 (C,D) 我们可以直接预处理出每一个块到上一个块的答案,然后询问的时候暴力跳即可,因为最多有(sqrt{n}) 个块。

    然后对于 (A,B,E,F) ,我们都可以直接在每次询问的时候暴力往上跳即可,这部分的复杂度不会超过 (sqrt{n})

    于是对于最后我们求出来的这 6 个集合取一个并集即可,使用 bitset 优化。

    时间复杂度 (O((n+m)(sqrt{n}+frac{c}{w})))

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    template <typename T>
    inline void read(T &x){
    	x=0;char ch=getchar();bool f=false;
    	while(!isdigit(ch)){if(ch=='-'){f=true;}ch=getchar();}
    	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    	x=f?-x:x;
    	return ;
    }
    template <typename T>
    inline void write(T x){
    	if(x<0) putchar('-'),x=-x;
    	if(x>9) write(x/10);
    	putchar(x%10^48);
    	return ;
    }
    const int N=4e4+5,C=4e4,B=205;
    int n,m,type,las,c[N];
    int head[N],nex[N<<1],to[N<<1],idx;
    int dep[N],key[N],keyid[N],bl[N],sta[N],top,tot;
    int fa[N],Fa[N];
    bool vis[N];
    void add(int u,int v){
    	nex[++idx]=head[u];
    	to[idx]=v;
    	head[u]=idx;
    	return ;
    }
    bitset<C> G[N/B+5][N/B+5],tmp;//块与块之间的根节点的答案和中间变量 
    void dfs(int x){
    	int now=top;dep[x]=dep[fa[x]]+1;
    	for(int i=head[x];i;i=nex[i]){
    		int y=to[i];
    		if(y==fa[x]) continue;
    		fa[y]=x;
    		dfs(y);
    		if(top-now>=B){//如果可以分成一个块 
    			key[++tot]=x;//key指当前块的根 
    			if(!keyid[x]) keyid[x]=tot;//keyid指该根节点所属的最初块编号 
    			while(top>now) bl[sta[top--]]=tot;//把所有点所属的块编号标记 
    		}
    	}
    	sta[++top]=x;//加入 
    	return ;
    }
    int b[N];
    int main(){
    	read(n),read(m);
    	for(int i=1;i<=n;i++) read(c[i]),b[i]=c[i];
    	sort(b+1,b+n+1);
    	int Idx=unique(b+1,b+n+1)-b-1;
    	for(int i=1;i<=n;i++) c[i]=lower_bound(b+1,b+Idx+1,c[i])-b;
    	for(int i=1;i<n;i++){
    		int u,v;
    		read(u),read(v);
    		add(u,v);
    		add(v,u);
    	}
    	dfs(1);
    	if(!tot) tot++;//如果分不出来,那就强行分 
    	if(keyid[key[tot]]==tot) keyid[key[tot]]=0;//如果最后一个块的根节点属于其本身(而不是属于0),那么置成0 
    	key[tot]=1;keyid[1]=tot;//把根节点置成最后一个块的根,根节点属于最后一个块  
    	while(top) bl[sta[top--]]=tot;//划分最后一个块 
    	for(int i=1;i<=tot;i++){//预处理
    		if(vis[key[i]]) continue;//预处理的是节点而不是块 
    		vis[key[i]]=true;//标记
    		tmp.reset();//重置 中间变量 
    		for(int u=key[i];u;u=fa[u]){//从当前点一直跳到根节点  
    			tmp[c[u]]=1;//把一路到根的颜色置成 1 
    			if(keyid[u]){
    				if(!Fa[key[i]]&&u!=key[i]) Fa[key[i]]=u;//新树上的父亲:Fa
    				G[keyid[key[i]]][keyid[u]]=tmp; //当前块到路径上每一个块的根节点 
    			}
    		}
    	}
    	while(m--){
    		tmp.reset();
    		int u,v,x,y;read(u),read(v);u^=las;x=u,y=v;
    		while(key[bl[x]]!=key[bl[y]]){
    			if(dep[key[bl[x]]]>dep[key[bl[y]]]){
    				if(x==u) while(x!=key[bl[u]]) tmp[c[x]]=1,x=fa[x];//若是第一次跳先暴力跳到关键点,则除非到了根,不然一直往上跳  
    				else x=Fa[x];//否则跳一整块
    			}
    			else{//同理 
    				if(y==v) while(y!=key[bl[v]]) tmp[c[y]]=1,y=fa[y];
    				else y=Fa[y];
    			}
    		}
    		if(keyid[x]) tmp|=G[keyid[key[bl[u]]]][keyid[x]];//大块之间的贡献 
    		if(keyid[y]) tmp|=G[keyid[key[bl[v]]]][keyid[y]];//右边的路径也一样 
    		while(x!=y){//在同一块 
    			if(dep[x]>dep[y]) tmp[c[x]]=1,x=fa[x];//一步一步跳 
    			else tmp[c[y]]=1,y=fa[y];//同理 
    		}
    		tmp[c[x]]=1;//记得根节点LCA处 
    		int Ans1=tmp.count(),Ans2=(~tmp)._Find_first();las=Ans1;
    		write(Ans1),putchar('
    ');
    	}
    	
    	return 0;
    } 
    
  • 相关阅读:
    【每日一题】740. 删除并获得点数
    【每日一题】1473. 粉刷房子 III
    【每日一题】7. 整数反转
    【每日一题】554. 砖墙
    【每日一题】690. 员工的重要性
    【每日一题】137. 只出现一次的数字 II
    【每日一题】403. 青蛙过河
    【每日一题】633. 平方数之和
    【每日一题】938. 二叉搜索树的范围和
    a_lc_到达终点(数学优化)
  • 原文地址:https://www.cnblogs.com/Akmaey/p/14666240.html
Copyright © 2011-2022 走看看