自认为不能将本题的各种细节和注意点讲得尽善尽美,因此将一位dalao的题解拖了过来,我的任务就是将代码写好,并稍微更改一下排版,使之阅读体验更佳。
题意:
有 T
条指令,指令格式分为以下两种:
M i j
:让第 i
号战舰所在列的全部战舰保持原有顺序,接在第 j
号战舰所在列的尾部。(_union
)
C i j
:询问第 i
号战舰与第 j
号战舰当前是否处于同一列中(if(find(i)==find(j))
),如果在同一列中,它们之间间隔了多少艘战舰。如果不在同一列,则输出“-1
”。
思路:
以前我们使用并查集都只是用来维护元素是否在一个集合中,通过这一题才了解到并查集的功能强大之处在于充分利用“边”。如果只用并查集来维护元素是否在一个集合内,其实根本没用到每个集合(树)里的边。学会充分利用边之后,并查集就会变成一个更强大的数据结构。
对于这一题,我们使用了边,规定每个节点与父节点的边的边权为1。这样节点路径上的边权之和就为树根到当前节点的距离。
那么,如果执行第二条指令查询i
、j
,若属于同一集合只需输出abs((x到树根的距离) - (y到树根的距离))-1
即可。
那么令数组d[x]
表示x
到父节点p[x]的距离,在查询也就是找树根的过程中,顺带将d[]
更新。
一、查询操作:find
int find(int x)
{
if(p[x]!=x)
{
int root = find(p[x]);
d[x]+=d[p[x]];//用dfs思想理解,先达到最底层,然后回溯时更新
p[x] = root;//路径压缩
}
return p[x];
}
这样,就可以保证每次用到d[x]
时,d[x]
表示为x
到树根的距离。
这里有个易错点,就是有同学可能会先更新
d[x]
,再p[x]=find(p[x])
。这样是错的。
因为显然我们要像搜索一样先递归到最底层,然后回溯的时候更新。如果像你那样写,父节点还没更新呢,你就用父节点的信息更新子节点,显然就错了嘛。
至此,查询操作已经完成了。
接下来就是:二、合并操作:_union
假设你将a
集合接到b
集合后边,实际上就是两棵树相接,思考一下,合并操作对这两棵树有什么影响。
对于
a
这棵树:每一个节点的d[]
都应该加上b
这棵树的大小siz[pb]
,因为是将a
接在b
屁股后面嘛。
对于b
这棵树:节点的d[]
不变,只不过合并后它的大小siz[pb]
要加上a
的大小siz[pa]
。
(pa = find(a)
, pb = find(b)
)
我们可以写出如下代码:
void _union(int a, int b)
{
int pa = find(a), pb = find(b);
if(pa!=pb)
{
d[pa] = siz[pb];
siz[pb]+=siz[pa];
p[pa] = pb;
}
}
关于这个代码:
①我们发现之前都没提到siz
数组,这很好办,在上面_union
函数之中的find
查询时跟d[]
一起维护更新即可。
②强调一下_union
函数里对于d[pa]
的更新:d[pa] = siz[pb]
;
其实,这是一个懒标记。
按理说,a
里头每一个节点的d[]
都应该加上siz[pb]
(别忘了现在是a
树合并到b
上,结合d[]
的定义很容易得出这个结论),那都要遍历更新一遍的话复杂度不就退化成O(n)
了嘛。那么我只需更新根节点的信息。然后在查询的时候,刚才没有被更新的节点再用根节点的信息来更新自己。
这样就可以保证用到才更新,相当于线段树里的懒标记。
时间复杂度:
并查集操作接近 O(1)
总时间复杂度O(nlogn)
代码:
#include<bits/stdc++.h>
using namespace std;
int t;
const int N = 3e4+10;
int p[N], d[N], siz[N];
void init(int n)
{
for(int i=1;i<n;++i) p[i] = i, siz[i] = 1;
}
int find(int x)
{
if(p[x]!=x)
{
int root = find(p[x]);
d[x]+=d[p[x]];
p[x] = root;
}
return p[x];
}
void _union(int a, int b)
{
int pa = find(a), pb = find(b);
if(pa!=pb)
{
d[pa] = siz[pb];
siz[pb]+=siz[pa];
p[pa] = pb;
}
}
int main()
{
cin>>t;
init(N);
while(t--)
{
char op[2]; int ii, jj;
scanf("%s%d%d",op, &ii, &jj);
if(op[0]=='M') _union(ii, jj);
else
{
int pii = find(ii), pjj = find(jj);
if(pii==pjj) printf("%d\n", max(0, abs(d[ii] - d[jj])-1));
else printf("-1\n");
}
}
return 0;
}