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('\n');
	}
	
	return 0;
} 
posted @ 2021-04-16 11:38  __Anchor  阅读(101)  评论(0编辑  收藏  举报