算法分析
并查集可以用于维护具有传递性关系的作用,每个集合的大小,绑定到根结点上,每个点到根结点的距离,绑定到每个元素的结点上
-
1、
size[x]
表示集合的大小,d[x]
表示x
到p[x]
的距离 -
2、将第
a
列的船接在第b
列的末尾,如图所示,相当于让每个点到pb
的距离都加上size[pb]
,由于存储并查集中存在路径压缩的效果,因此只需要将pa
到pb
的距离加上size[pb]
即可,即d[pa] = size[pb]
,其他跟着pa
后面的元素会通过图片2
操作对路径进行压缩且更新d[i]
的值 -
3、路径压缩的过程
-
1、找到根结点
root
-
2、计算父节点到上一个父节点的距离
d[p[x]]
,同时进行路径压缩(路径压缩后,上一父节点即root
) -
3、
d[x]
初始值是x
到p[x]
的距离,更新d[x]
值,即d[x] = d[x] + d[p[x]]
-
注意:题目C
操作问的是间隔多少条船,若a != b
,则间隔abs(d[a] - d[b]) - 1
条船,若a == b
,则间隔0
条船
时间复杂度
并查集操作接近$O(1)$
参考文献
算法提高课
Java 代码
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class Main {
static int N = 30010;
static int n,k;
static int[] p = new int[N];
static int[] size = new int[N];//集合的大小
static int[] d = new int[N];//到根结点的距离
static int find(int x)
{
if(p[x] != x)
{
int root = find(p[x]);
d[x] += d[p[x]];
p[x] = root;
}
return p[x];
}
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter log = new BufferedWriter(new OutputStreamWriter(System.out));
n = Integer.parseInt(br.readLine());
for(int i = 1;i <= 30000;i ++)
{
p[i] = i;
size[i] = 1;
}
for(int i = 0;i < n;i ++)
{
String[] s1 = br.readLine().split(" ");
String op = s1[0];
int a = Integer.parseInt(s1[1]);
int b = Integer.parseInt(s1[2]);
if(op.equals("M"))
{
int pa = find(a);
int pb = find(b);
p[pa] = pb;
d[pa] = size[pb];
size[pb] += size[pa];
}
else
{
int pa = find(a);
int pb = find(b);
if(pa != pb) log.write(-1 + "\n");
else log.write(Math.max(0, Math.abs(d[a] - d[b]) - 1) + "\n");
}
}
log.flush();
}
}
这里在合并集合的时候应该加一个判断,如果是合并操作:
if(pa == pb) continue;
代表如果ab已经属于同一列,并不需要维护
M操作时需要加一个条件判断,具体如下:
if(op.equals(“M”))
{
int pa = find(a);
int pb = find(b);
if(pa!=pb){
p[pa] = pb;
d[pa] = size[pb];
size[pb] += size[pa];
}
}
good
小呆呆yyds
○| ̄|_
为什么d[x] += d[p[x]];;这句话不能写在寻找父亲节点int root = find(p[x]);的前面
在合并两个集合(a、b)的时候,我们是先让 集合a 的 根节点的距离 变成到 集合b 根节点的距离,但是 d[pa] = size[pb] 这一步并没有实际更新节点之间的距离,所以需要通过路径压缩的过程来完成,这时候假设先路径压缩集合 a,这时候集合a 的根节点的d[x]存储的是到 集合b根节点的距离,而集合a 中其他节点却没有更新,所以我们需要从上往下更新信息,而不是从下往上,从下往上算出来的值,只是在当前节点又加上了一次到原来根节点的值,并不是到集合b根节点的距离,也就只有父节点是根节点的节点才会更新正确
错误的,冰茶姬路径压缩是logn的,路径压缩+启发式合并是反阿克曼的,不存在O(1)的冰茶姬
存在严格线性的并查集的,感兴趣可以看下OIwiki里面的介绍
把 pa 节点所在列接到 pb 所在列的时候,为什么更新距离的表达式使用
d[pa] = size[pb]
而非d[pa]+=size[pb]
同问
模拟了一遍样例好像明白了,在询问的时候会先find根节点,这个过程会从下往上遍历,最后d[x]存的就是从x到root根节点的距离。但将两个队列合并,d[pa]存的是从pa到pb的距离,树就是通过M操作来构造的
都可以呀,因为 $a$ 集合的根节点到 $a$ 集合的根节点的距离是 $0$
请问 最后的max是啥意思
C 4 4 这个测试数据不max就是-1
有可能会判断同一个战舰之间的数量
将d[pa] = size[pb]改成d[pa] += size[pb];也可以ac欸 感觉更符合“因此只需要将pa到pb的距离加上size[pb]即可”这句话
根节点是0所以不会影响