DGZX1521 - 银河英雄传说
经过分析,不难知道本题需要维护一个数据结构,使之支持以下操作:
1. 合并战舰集合(M i j);
2. 查询2艘战舰是否在同一集合/列(C i j);
3. 如果在同一集合,求出它们的相对位置。
标准的并查集只支持合并和查找操作,并不支持求集合中两元素之间相对距离的操作。所以我们要用扩展并查集。标准并查集中每个节点只有一个域,即用father[i] 表示节点 i 的父亲。本题可以扩展出两个域:
用total[i] 表示以 i 为根的集合中的元素个数,它当且仅当i是根时有意义;
用front[i] 表示排在 i 前面有多少个点,front[root]=0。
若合并操作令 father[a] = b(a 和 b 都是根),那么同时必须令:
front[a] = total[b];
total[b] = total[b] + total[a];
PS:total[a] 不需要更新,因为它已经不再是根节点了;front[b]也不需要更新,因为没有发生变化。
执行查找操作时,由于每个集合的根节点都没有发生变化,所有不需要改变total域,只需要修改front域。执行find(i) 时,要将节点i到根节点路径上的所有必经点的front累加起来,作为该点新的front值。因为front值从某种意义上来说,表示的是一个点失去“根节点”的地位的那一刻其前面有多少个点。
比如有8个点:

依次执行:合并1和2;合并3和4;合并5和6;合并7和8。

再依次执行:合并1和3;合并5和7。

再合并1和5。

树的形状:

队列中节点依次应是:1、2、3、4、5、6、7、8。
观察各个节点的 front域,发现它们都停留在该节点失去“根节点”地位那一瞬间的状态,记录的是那一瞬间使它丧失“根节点”地位的子树一共有多少个点,也即那一瞬间排在它前面的有多少点。
如果执行 find(6),执行前:
front(6) = 1,对应着节点6前面的节点5;
front(5) = 4,对应着节点5前面的节点1~4;
front(1) = 0,意味着1就是根节点。
执行find(6),会导致front(6) = front(6) + front(5) + front(1) = 5,对应着节点6前面的节点1~5。
如果再执行一次find(6),front(6) = front(6) + front(1) = 5,还是不变。
【C++版本】
#include <stdio.h>
int fa[30001]; // i 的父亲,初始值为 i 自己
int s[30001]; // i 所在队列中 i 前面元素的个数,初始值为 0
int t[30001]; // i 所在队列中所有元素总个数,初始值为 1
int m;
inline int ABS(int a)
{
return ((a>0)?(a):(-a));
}
int find(int v)
{
int root = v;
while (fa[root]!=root)
root = fa[root];
int p = v, tmp, j;
while (p!=root)
{
tmp = fa[p];
fa[p] = root;
j = tmp;
while (fa[j]!=j)
{
s[p] += s[j];
j = fa[j];
}
p = j;
}
return root;
}
int main()
{
int i, fx, fy, x, y;
char ch;
scanf("%d",&m);
for (i=1; i<=30000; i++)
{
fa[i] = i;
s[i] = 0;
t[i] = 1;
}
for (i=1; i<=m; i++)
{
scanf("\n%c %d %d",&ch,&x,&y);
fx = find(x);
fy = find(y);
if (ch=='M')
{
if (fx!=fy)
{
fa[fx] = fy;
s[fx] = t[fy];
t[fy] += t[fx];
}
}
else
{
if (fx!=fy)
printf("-1\n");
else printf("%d\n",ABS(s[x]-s[y])-1);
}
}
return 0;
}
【Pascal版本】
// 140K 1136MS Pascal 1.36K
// d[i]表示它在它所处集合中的深度(即离根的距离),d[root]=0;
// l[i]表示以i为根的集合中的元素个数,它当且仅当i是根时有意义。
const
maxn=30000;
type
node=record
p,d,l :integer;
end;
var
uf :array[1..maxn]of node; //并查集
n,m :longint;
a,b,i :longint;
c :char;
procedure init;
var
i :integer;
begin
//assign(input,'galaxy.in');reset(input);
//assign(output,'galaxy.out');rewrite(output);
n:=30000;
readln(m);
for i:=1 to n do //初始化并查集:p[i]=i,l[i]=1
with uf[i] do begin p:=i; l:=1 end;
end;
function find(x:integer):integer;
var
i,j,p,q :integer;
begin
i:=x;
while uf[i].p<>i do i:=uf[i].p; //查找代表元
p:=i;
i:=x;
while i<>p do
begin
q:=uf[i].p;
uf[i].p:=p; //路径压缩
j:=q;
repeat
inc(uf[i].d,uf[j].d); //更新d值
j:=uf[j].p
until uf[j].p=j;
i:=q;
end;
find:=p;
end;
procedure union(a,b:integer);
var
t :integer;
begin
a:=find(a); b:=find(b);
uf[a].p:=b; //合并
uf[a].d:=uf[b].l; //更新d
inc(uf[b].l,uf[a].l) //更新l
end;
{=========main========}
begin
init;
for i:=1 to m do
begin
readln(c,a,b);
if c='M' then union(a,b);
if c='C' then
if (find(a)=find(b)) then
writeln(abs(uf[a].d-uf[b].d)-1)
else writeln(-1)
end;
//close(output)
end.

浙公网安备 33010602011771号