[2017CCPC杭州] HDU 6271 树分块+并查集

给出 n ≤ 1 e 4 n\leq1e4 n1e4的两颗树 T a , T b T_a,T_b Ta,Tb,以及一个 m ≤ 1 e 4 m\leq1e4 m1e4个点的图 G G G,树上的每个结点都代表图 G G G上的一条边,一开始图上没有连边。然后要求出对于 ∀ i ∈ [ 1 , n ] \forall i \in [1,n] i[1,n],把两颗树各自的根到 i i i结点路径上的所有的点上的边加入图中,这个图的连通块的个数。
考虑用可撤销的并查集维护连通块的个数。问题在于遍历到第一个树每个结点 i i i的时候,想要维护连通的信息,在第二颗树上必须要遍历整个树,复杂度 O ( ( n l o g n ) 2 ) O((nlogn)^2) O((nlogn)2),效率太低。
一个好的思路是,遍历第二颗树能够多处理一些点。把第一个树按照点的距离 ≤ n \leq\sqrt n n 分块,这样的点不超过 n \sqrt n n 个,把一个块的点信息放在一个点上。
在第一个树上处理的时候,只有遇到关键点,才会遍历第二颗树,而此时同时处理 n \sqrt n n 个点,第一颗树只有 n \sqrt n n 个点会去遍历第二颗树。而第二颗树也只有遇到 n \sqrt n n 个点才会去处理第一颗树的块内信息,块的大小又是小于等于 n \sqrt n n 的,所以遍历第二颗树的复杂度不会超过 O ( n ) O(n) O(n)
总复杂度是 O ( n n l o g n ) O(n\sqrt nlogn) O(nn logn)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int N=1e4+7;
int B;
vector<int> go1[N],go2[N];
struct edge { int u,v; }p1[N],p2[N];
int fa[N],dis[N],c[N],bel[N];
int ans[N];  

struct Union { 
	int f[N],sz[N],stk[N];
	int top=0,sum=0;
	void init(int n) {
		for(int i=1;i<=n;i++)
			f[i]=i,sz[i]=1;
		sum=n;top=0;
	}
	int find(int x) {
		if(f[x]==x) return x;
		else return find(f[x]);
	}
	void merge(int x,int y) {
		x=find(x);y=find(y);
		if(x==y) return;
		if(sz[x]>sz[y]) swap(x,y);
		sum--;f[x]=y;sz[y]+=sz[x];stk[++top]=x;
	}
	void undo(int s) {
		while(top>s) {
			int x=stk[top];top--;
			sz[f[x]]-=sz[x];
			f[x]=x;
			sum++;
		}
	}
}U;

void divide(int u,int f) {
	fa[u]=f;
	dis[u]=0,c[u]=0;
	for(auto &v:go1[u]) {
		if(v==f) continue;
		divide(v,u);
		if(!c[v]) dis[u]=max(dis[u],dis[v]+1);
	}
	if(dis[u]==B) c[u]=1;
} 
int tag;
void dfs2(int u,int f) {
	int cur=U.top;
	U.merge(p2[u].u,p2[u].v);
	for(auto &v:go2[u]) {
		if(v==f) continue;
		dfs2(v,u);
	}
	if(bel[u]!=tag) {
		U.undo(cur);
		return;
	}
	for(int i=u;i!=tag;i=fa[i]) 
		U.merge(p1[i].u,p1[i].v);
	ans[u]=U.sum;
	U.undo(cur); 
}
void dfs1(int u,int f) {
	int cur=U.top;
	U.merge(p1[u].u,p1[u].v);
	for(auto &v:go1[u]) {
		if(v==f) continue;
		dfs1(v,u); 
	}
	if(!c[u]) { U.undo(cur); return; }
	tag=u; 
	dfs2(1,0);
	U.undo(cur);  
}
int main() {
	int T;
	scanf("%d",&T);
	while(T--) {
		int n,m;
		scanf("%d%d",&n,&m);
		memset(go1,0,sizeof(go1));
		memset(go2,0,sizeof(go2));
		U.init(m);
		for(int i=1;i<=n;i++) 
			scanf("%d%d",&p1[i].u,&p1[i].v);
		for(int i=1;i<n;i++) {
			int u,v;
			scanf("%d%d",&u,&v);
			go1[u].push_back(v);
			go1[v].push_back(u);
		}
		for(int i=1;i<=n;i++)
			scanf("%d%d",&p2[i].u,&p2[i].v);
		for(int i=1;i<n;i++) {
			int u,v;
			scanf("%d%d",&u,&v);
			go2[u].push_back(v);
			go2[v].push_back(u);
		}
		B=sqrt(n);
		divide(1,0);
		c[1]=1; 
		for(int j,i=1;i<=n;i++) {
			for(j=i;c[j]!=1;j=fa[j]);
			bel[i]=j;
		}
		dfs1(1,0); 
		for(int i=1;i<=n;i++)
			printf("%d\n",ans[i]); 
	} 
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值