洛谷 P1196 银河英雄传说 (带权并查集)

题目描述:

题目传送门


解题思路:

根据数据范围以及题目中提到的合并等操作可以联想用并查集求解。
但是仔细想想,普通并查集只能判断某元素是否为统一集合的联通性问题,而题目中提到了“战舰数量”等权值性的问题,这种时候我们就要对并查集进行一些改变,成为可以处理权值的带权并查集

  • 带权并查集

必须要了解的前置知识是并查集(废话),百度食用更佳。

带权并查集其实和普通并查集没有本质的区别,仅仅只是需要在路径压缩或合并集合的时候维护或更新节点与父节点的边权关系以及维护该集合内元素个数。

f a x fa_{x} fax 表示从 x x x x x x 的父节点的边权, d i s x dis_x disx 表示从 x x x 到达 f a x fa_x fax 的边权, c n t x cnt_x cntx 表示为元素 x x x 所在的集合的元素个数。

  1. 考虑合并集合 a a a b b b ,我们可以让集合 a a a 的根节点与集合 b b b 的根节点相连,这样 a a a 就成为了 b b b 的一颗子树。可以的之,连接后 a a a 子树的深度肯定是位于原本 b b b 所有子树的下方,也就是说, a a a 的根节点 x x x 到大 b b b 的根节点 y y y 的距离是原本 b b b 子树的元素个数,也就是说 d i s x = c n t y dis_x=cnt_y disx=cnty。也很容易理解,由于加上了子树 a a a,那么 b b b 的元素个数为 c n t x + c n t y cnt_x+cnt_y cntx+cnty
  2. 考虑路径压缩操作,所有节点的父亲都为跟,也就是说我们要维护使得所有节点与根的边权不变。操作后, f a x fa_x fax 节点的父亲变为集合的根,也就是说 f a f a x fa_{fa_x} fafax 是这个集合的跟,那么 x x x 点到达根的距离为: d i s x = d i s x + d i s f a x dis_x=dis_x+dis_{fa_x} disx=disx+disfax。这句话的解析其实就是该节点到达父节点的距离,再加上父节点到达根的距离,即为该节点到达根的距离。

带权并查集的查询:

int find(int x)
{
	if(fa[x]==x) return x;
	int t=find(fa[x]);  
	 //必须要有这么一赋值步骤,因为复制中find函数已经运行过一次了,此时 fa[fa[x]]为根节点,与分析相符;否则 fa[fa[x]]未经过改变,不为根节点,与分析不符
	dis[x]+=dis[fa[x]];  //更新当前点到根节点的距离
	fa[x]=t;
	return fa[x];
}

关于赋值操作的必要性请看此贴

合并集合操作:

void join(int x,int y)
{
	int a=find(x),b=find(y);
	fa[a]=b;
    dis[a]=cnt[b];  //更新根节点之间的距离
	cnt[b]+=cnt[a];  //更新集合元素个数
	cnt[a]=cnt[b];
}

知道了带权并查集的思想,这道题就不难解除。


CODE:

#include <iostream>
#include <cmath>
using namespace std;
int n;
int fa[50010],dis[50010]={0},cnt[50010];
int find(int x)
{
	if(fa[x]==x) return x;
	int t=find(fa[x]); 
	dis[x]+=dis[fa[x]];
	fa[x]=t;
	return fa[x];
}
void join(int x,int y)
{
	int a=find(x),b=find(y);
	fa[a]=b;
    dis[a]=cnt[b];
	cnt[b]+=cnt[a];
	cnt[a]=cnt[b];
}
int main()
{
	std::ios::sync_with_stdio(false);  //关闭输入输出同步流
	cin>>n;
	char pos;
	int a,b;
	for(int i=1;i<=30000;i++) fa[i]=i,cnt[i]=1;
	for(int i=1;i<=n;i++)
	  {
	  	cin>>pos>>a>>b;
	  	if(pos=='M')
	  	  {
	  	  	if(find(a)!=find(b))join(a,b);
	  	  }
	  	if(pos=='C')
	  	  {
	  	  	if(find(a)!=find(b)) cout<<-1<<endl;
	  	  	else cout<<abs(dis[a]-dis[b])-1<<endl;
	  	  }
	  }
	return 0;
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值