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.

 

posted @ 2013-12-20 16:34  莞中OI  阅读(357)  评论(0)    收藏  举报