并查集(维护每个连通块的大小和每个点到根节点的距离版本)
例题
Acwing 238.银河英雄传说
有一个划分为N列的星际战场,各列依次编号为1,2,…,N。
有N艘战舰,也依次编号为1,2,…,N,其中第i号战舰处于第i列。
有T条指令,每条指令格式为以下两种之一:
1、M i j,表示让第i号战舰所在列的全部战舰保持原有顺序,接在第j号战舰所在列的尾部。
2、C i j,表示询问第i号战舰与第j号战舰当前是否处于同一列中,如果在同一列中,它们之间间隔了多少艘战舰。
现在需要你编写一个程序,处理一系列的指令。
输入格式
第一行包含整数T,表示共有T条指令。
接下来T行,每行一个指令,指令有两种形式:M i j或C i j。
其中M和C为大写字母表示指令类型,i和j为整数,表示指令涉及的战舰编号。
输出格式
你的程序应当依次对输入的每一条指令进行分析和处理:
如果是M i j形式,则表示舰队排列发生了变化,你的程序要注意到这一点,但是不要输出任何信息;
如果是C i j形式,你的程序要输出一行,仅包含一个整数,表示在同一列上,第i号战舰与第j号战舰之间布置的战舰数目,如果第i号战舰与第j号战舰当前不在同一列上,则输出-1。
代码
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 #include <algorithm> 5 #include <cmath> 6 7 using namespace std; 8 9 const int N = 30010; 10 int p[N], s[N], d[N]; 11 12 int find(int x) 13 { 14 if (p[x] != x) 15 { 16 int u = find(p[x]); 17 d[x] += d[p[x]]; 18 p[x] = u; 19 } 20 return p[x]; 21 } 22 23 int main() 24 { 25 for (int i = 1; i <= 30005; i ++ ) 26 { 27 p[i] = i; 28 s[i] = 1; 29 } 30 31 int t; 32 cin >> t; 33 while (t -- ) 34 { 35 char s[2]; int a, b; 36 scanf("%s%d%d", s, &a, &b); 37 if (s[0] == 'M') 38 { 39 int fa = find(a), fb = find(b); 40 if (fa == fb) continue;//特判,如果两个节点已经在同一个集合中时我们接下来的操作相当于使这个集合的大小翻倍 41 d[fa] = s[fb]; 42 s[fb] += s[fa]; 43 p[fa] = fb; 44 } 45 else 46 { 47 int fa = find(a), fb = find(b); 48 if (fa != fb) printf("-1\n"); 49 else printf("%d\n", abs(d[a] - d[b]) - 1);//由于是询问相隔多少个战舰,所以我们需要-1 50 } 51 } 52 53 return 0; 54 }
思路
s和d数组分别维护每个连通块的大小和每个节点到根节点的距离。
以下以a代表a连通块,b代表b连通块。
当查询到两个节点没有在同一个连通块中时,就将两个连通块合并到一起,并且使得新生成的连通块的根节点的s等于开始两个连通块的s之和,即将a的根节点指向b的根节点,则新生成的连通块的根节点即原来b的根节点,此时只需使sb加上sa即可得到新连通块的大小。此时我们a中每一个节点的d都是指向a的根节点的距离,b中每一个节点的d都是指向b的根节点的距离,由于我们在逻辑上是将a接在b之后,所以我们原来b中的b数组就不需要改变,而a的则需要改变。
这时我们只需将a的根节点的d指向b的根节点即使d等于b的大小即可,这样由于我们下一次查询a的根节点时会查询到a的根节点,再由a的根节点查询到b的根节点,那么我们在查询到时只需让a中的d加上a的根节点的d,同理可知我们只需在查询时让当前的节点的d加上其父亲节点的d即可。

 
                
             
         浙公网安备 33010602011771号
浙公网安备 33010602011771号