[模板] 并查集 - 带权并查集 (洛谷 P3367 银河英雄传说)

P1196 [NOI2002]银河英雄传说

带权并查集

题目背景

公元5801年,地球居民迁至金牛座α第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展。

宇宙历799年,银河系的两大军事集团在巴米利恩星域爆发战争。泰山压顶集团派宇宙舰队司令莱因哈特率领十万余艘战舰出征,气吞山河集团点名将杨威利组织麾下三万艘战舰迎敌。

题目描述

杨威利擅长排兵布阵,巧妙运用各种战术屡次以少胜多,难免恣生骄气。在这次决战中,他将巴米利恩星域战场划分成30000列,每列依次编号为1,2,···,30000。之后,他把自己的战舰也依次编号为1,2···,30000,让第i号战舰处于第i列,形成“一字长蛇阵”,诱敌深入。这是初始阵形。当进犯之敌到达时,杨威利会多次发布合并指令,将大部分战舰集中在某几列上,实施密集攻击。合并指令为M i j,含义为第i号战舰所在的整个战舰队列,作为一个整体(头在前尾在后)接至第j号战舰所在的战舰队列的尾部。显然战舰队列是由处于同一列的一个或多个战舰组成的。合并指令的执行结果会使队列增大。

然而,老谋深算的莱因哈特早已在战略上取得了主动。在交战中,他可以通过庞大的情报网络随时监听杨威利的舰队调动指令。

在杨威利发布指令调动舰队的同时,莱因哈特为了及时了解当前杨威利的战舰分布情况,也会发出一些询问指令:C i j。该指令意思是,询问电脑,杨威利的第i号战舰与第j号战舰当前是否在同一列中,如果在同一列中,那么它们之间布置有多少战舰。

作为一个资深的高级程序设计员,你被要求编写程序分析杨威利的指令,以及回答莱因哈特的询问。

题目大意

现有n个元素排成n列,即每个元素自称成一列,有两种操作:

  • M i j:表示将i队列的头接至j队列的尾,合并指令的执行结果会使i队列增大,j队列的元素数量成为0

  • C i j:询问ij是否在同一个队列中,若是,输出ij之间的元素数量,否则输出-1

输入格式

第一行有一个整数T (1≤T≤5×1E5),表示总共有T条指令。

以下有T行,每行有一条指令。指令有两种格式:

  • M i jij是两个整数(1≤i,j≤30000),表示指令涉及的编号。该指令是合并指令,并且保证i元素与j元素不在同一列。

  • C i jij是两个整数(1≤i,j≤30000),表示指令涉及的编号。该指令是询问指令。

输出格式

对于询问指令,你的程序要输出一行,仅包含一个整数,表示在同一列上,i号与j号元素之间的元素数量。如果i号与j号元素当前不在同一列上,则输出 −1

输入输出样例

输入 输出
4
M 2 3
C 1 2
M 2 4
C 4 2
-1
1

说明/提示

战舰位置图:表格中阿拉伯数字表示战舰编号
[模板] 并查集 - 带权并查集 (洛谷 P3367 银河英雄传说)_第1张图片

思路

带权并查集实现。在一般并查集基础上增加dep记录所在集的位置上的深度(模拟链表),在队列头记录该集下的元素数量,同时带有路径压缩优化实现。

代码

//cpp
#include 
#include  //abs

using ::std::cin;
using ::std::cout;

struct ufsets_elem_w{
	int dep, vn;		 //dep表示元素在队列上的位置,vn=0表示不是队列头,vn有效时表示这个元素是队列头,值等于这个队列上的元素数量。
	ufsets_elem_w *root; //root表示该元素所在队列的第一个元素
	ufsets_elem_w(){
		dep = 0, vn = 1;
		root = this;
	}
	ufsets_elem_w *find(){
		if (this != root){					   //该节点不是根节点
			ufsets_elem_w *tmp = root->find(); //带路径压缩的根节点记录(缓存了更新后的根节点值)
			dep += root->dep;				   //该节点深度+=更新前的根节点深度
			root = tmp;						   //更新根节点
		}
		return root;
	}
};

struct ufsets_w{				//带权并查集
protected:
	int ufsets_num;				//独立集合数量
	ufsets_elem_w *ufsets_base; //并查集数组
public:
	explicit ufsets_w(const int &n){
		ufsets_base = new ufsets_elem_w[n + 1]();
		ufsets_num = n;
	}
	bool catenate(const int &a, const int &b){	   //合并a,b集合,返回false则说明两元素已是同一集合
		ufsets_elem_w *rb = ufsets_base[b].find(); //路径压缩b节点并临时缓存(下一步会更改root,必须缓存)
		if (ufsets_base[a].find() != rb){		//路径压缩a节点,若a,b不在同一集合则合并
			--ufsets_num;
			rb->dep += ufsets_base[a].root->vn; //b所在队首深度更新至this队尾
			rb->root = ufsets_base[a].root;		//b所在队首根更新为this的根
			ufsets_base[a].root->vn += rb->vn;	//a所在队首记录的元素数量+=b队列的数量
			rb->vn = 0;							//取消b队列的队列头状态
			return true;
		}
		return false;
	}
	bool relative(const int &a, const int &b){ //查询是否在同一个集合中,是则返回true
		return ufsets_base[a].find() == ufsets_base[b].find();
	};
	int depth(const int &pos){//元素位于所在队列的哪一个位置
		return ufsets_base[pos].dep;
	}
	int size(){//独立集合数量
		return ufsets_num;
	}
	~ufsets_w(){
		delete[] ufsets_base;
	}
};

const int NMAX = 30000 + 5;

int main(){
	int t;
	char tmp1;
	int tmp2, tmp3;
	::std::ios::sync_with_stdio(false);
	cin >> t;
	ufsets_w u_w(NMAX);
	for (int i = 1; i <= t; ++i){
		cin >> tmp1 >> tmp2 >> tmp3;
		switch (tmp1){
		case 'M':
			u_w.catenate(tmp3, tmp2); //把tmp2的队列移动到tmp3所在队列的后面
			break;
		case 'C':
			if (!u_w.relative(tmp2, tmp3)) //tmp2,tmp3不在同一集合中
				cout << "-1\n";
			else
				cout << abs(u_w.depth(tmp2) - u_w.depth(tmp3)) - 1 << '\n'; //中间相隔的元素数量,需要-1
			break;
		}
	}
	return 0;
}
//java
import java.util.*;
import java.math.*;

class ufsets_elem_w{
	int dep, vn;		 //dep表示元素在队列上的位置,vn=0表示不是队列头,vn有效时表示这个元素是队列头,值等于这个队列上的元素数量。
	ufsets_elem_w root; //root表示该元素所在队列的第一个元素
	ufsets_elem_w(){
		dep = 0;
		vn = 1;
		root = this;
	}
	ufsets_elem_w find(){
		if (this != root){					   //该节点不是根节点
			ufsets_elem_w tmp = root.find(); //带路径压缩的根节点记录(缓存了更新后的根节点值)
			dep += root.dep;				   //该节点深度+=更新前的根节点深度
			root = tmp;						   //更新根节点
		}
		return root;
	}
};

class ufsets_w{				//带权并查集
	int ufsets_num;				//独立集合数量
	ufsets_elem_w ufsets_base[]; //并查集数组
	ufsets_w(final int n){
		ufsets_num = n;
		ufsets_base = new ufsets_elem_w[n + 1];
		for(int i=1;i<=n;i++)//ufsets_base[0]用不到
			ufsets_base[i]=new ufsets_elem_w();
		ufsets_num = n;
	}
	boolean catenate(final int a, final int b){	   //合并a,b集合,返回false则说明两元素已是同一集合
		ufsets_elem_w rb = ufsets_base[b].find(); //路径压缩b节点并临时缓存(下一步会更改root,必须缓存)
		if (ufsets_base[a].find() != rb){		//路径压缩a节点,若a,b不在同一集合则合并
			--ufsets_num;
			rb.dep += ufsets_base[a].root.vn; //b所在队首深度更新至this队尾
			rb.root = ufsets_base[a].root;		//b所在队首根更新为this的根
			ufsets_base[a].root.vn += rb.vn;	//a所在队首记录的元素数量+=b队列的数量
			rb.vn = 0;							//取消b队列的队列头状态
			return true;
		}
		return false;
	}
	boolean relative(final int a, final int b){ //查询是否在同一个集合中,是则返回true
		return ufsets_base[a].find() == ufsets_base[b].find();
	}
	int depth(final int pos){//元素位于所在队列的哪一个位置
		return ufsets_base[pos].dep;
	}
	void refresh(final int end) {//针对java的特殊优化,刷新各节点存储的root值(防止StackFlowError)
		for(int i=1;i<=end;i++)
			ufsets_base[i].find();
	}
	int size(){//独立集合数量
		return ufsets_num;
	}
};


public class Main {
	
	final static int MAXN=30000;

	public static void main(String[] args) {
		Scanner sc=new Scanner(System.in);
		int t=sc.nextInt();
		int max_use=0,m_tm=0;//针对java的特殊优化
		ufsets_w u_w=new ufsets_w(MAXN);
		for (int i = 0; i <= t; ++i){
			String s[]=sc.nextLine().split(" ");
			switch (s[0]){
			case "M":
				{//针对java的特殊优化,刷新各节点存储的root值(防止StackFlowError)
					m_tm++;
					max_use=Math.max(Math.max(Integer.valueOf(s[2]),Integer.valueOf(s[1])),max_use);
					if(m_tm%173==0){
						u_w.refresh(max_use);
					}
				}
				u_w.catenate(Integer.valueOf(s[2]), Integer.valueOf(s[1])); //把s[1]的队列移动到s[2]所在队列的后面
				break;
			case "C":
				if (!u_w.relative(Integer.valueOf(s[1]), Integer.valueOf(s[2]))) //不在同一集合中
					System.out.println("-1");
				else
					System.out.println(Math.abs(u_w.depth(Integer.valueOf(s[1])) - u_w.depth(Integer.valueOf(s[2]))) - 1); //中间相隔的元素数量,需要-1
				break;
			}
		}
	}
}

你可能感兴趣的:(算法)