从一道题看加权并查集 【POJ 1988】

##题意概述

就是说在地面上存在一堆木块,你要执行p次操作,把x放到y上,然后询问你某一个木块的集合的数量

要注意的是当你移动一个木块(假设为A)到B上面去的时候,是把A整个集合移动到B上,这一点看样例就可以了解到

这很明显是一个典型的并查集的题目,当把A移动到B上面去的时候,相当于把b垫在了脚底下,这也就是我们在并查集中的父子关系,由上面易得A是B的父节点(father)。


但是难处理的是,如何找出任意结点下面有多少个结点,一个很自然的思路会想到我们先创建一个f_node数组用来记录该结点所在的树有多少个结点。但是光有结点仍然没有办法求出题目要求的数据,这时我们需要先再来讲一下什么叫路径压缩

int find(int x){
    if(x != f[x]) f[x] = find(f[x]);//一个标准的路径压缩模板
    return f[x];
}

假设有这样一棵树

当我们find开始寻找的时侯,find会一步一步地递归,直到结点编号=其父节点(根节点)的编号,然后再依次回溯回去给每一个树上的结点赋值为该根节点,
也就是如下图所述,这样下次我们去查询的时候就会非常快

在这里插入图片描述

那通过这样一个流程可以发现我们可以很轻松的在路径压缩的过程中找出当前树中任意结点到该树根节点的距离也就是f_dis,通过这样的关系我们轻松可以得出关系 任意结点下的结点数 = f_node(该子树) - f_dis[该木块] - 1

#include<iostream>
using namespace std;
int f[100005];//并查集的集合
int f_node[100005];//第i个树中有多少个结点
int f_dis[100005];//当前树中任意结点到该树根节点的距离
int find(int x) {
	if (x != f[x]) {
		int now = f[x];
		f[x] = find(f[x]);
		f_dis[x] = f_dis[x] + f_dis[now];//相当于从上到下依次相加距离
	}
	return f[x];
}
void merge(int x, int y) {
	int fx = find(x);
	int fy = find(y);
	if (fx != fy) {
		f[fy] = fx;
		f_dis[fy] = f_dis[fy] + f_node[fx];//把fy接到fx下面去,就不难知道fy到根节点的值要加上fx这颗树的结点数
		f_node[fx] = f_node[fx] + f_node[fy];//加上俩树的总共结点数量
	}
}
void init() {
	for (int i = 1; i <= 40000; ++i) {
		f[i] = i;
		f_node[i] = 1;
		f_dis[i] = 0;//注意初始化
	}
}
int main() {
	int t;
	cin >> t;
	init();
	while (t--) {
		char op;
		cin >> op;
		if (op == 'M') {
			int x, y;
			cin >> x >> y;
			merge(x, y);
		}
		if (op == 'C') {
			int x;
			cin >> x;
			int fx = find(x);
			cout << f_node[fx] - f_dis[x] - 1 << endl;
		}
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值