带权并查集
公元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
:询问i
与j
是否在同一个队列中,若是,输出i
与j
之间的元素数量,否则输出-1
。
第一行有一个整数T (1≤T≤5×1E5)
,表示总共有T
条指令。
以下有T
行,每行有一条指令。指令有两种格式:
M i j
:i
和j
是两个整数(1≤i,j≤30000)
,表示指令涉及的编号。该指令是合并指令,并且保证i
元素与j
元素不在同一列。
C i j
:i
和j
是两个整数(1≤i,j≤30000)
,表示指令涉及的编号。该指令是询问指令。
对于询问指令,你的程序要输出一行,仅包含一个整数,表示在同一列上,i
号与j
号元素之间的元素数量。如果i
号与j
号元素当前不在同一列上,则输出 −1
。
输入 | 输出 |
---|---|
4 M 2 3 C 1 2 M 2 4 C 4 2 |
-1 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;
}
}
}
}