边带权的并查集--银河英雄传说 NOI2002/CODEVS1540

题干:
有一个划分成N列的星际战场,各列依次编号为1,2,…,N。有N艘战舰,也依次编号为1,2,…,N,其中第i号战舰处于第i列。
有M条指令,每条指令格式为以下两种之一:

  1. M i j,表示让第i号战舰所在列的全部战舰保持原有顺序,接在第j号战舰所在列的尾部。
  2. C i j,表示询问第i号舰艇与第j号舰艇当前是否处在同一列中,如果在同一列中,他们之间隔了多少艘战舰。如果第i号战舰与第j号战舰当前不在同一列上,则输出-1。
    N<=3000 M<=5*105
    样例输入 Sample Input
    4
    M 2 3
    C 1 2
    M 2 4
    C 4 2
    样例输出 Sample Output
    -1
    1
    思路:一条“链"也是一棵树,只不过是树的特殊形态。因此可以把一列战舰看作一个集合,用并查集维护。最初,N个战舰构成N个独立的集合。
    fa[x]表示在第x号战舰前面的那个战舰号
    一个集合的代表就是位于最前边的战舰
    让树的每条边带权值1,这样树上两点间的距离-1就是间隔的战舰数量。
    在考虑路径压缩的情况下,额外建立一个数组d,d[x]记录战舰x与
    fa[x]之间的边的权值。
    在路径压缩时把x直接指向树根的同时,同时更新d[x]为x到树根的路径上所有边权之和。
    对get函数稍加修改,就可以实现对d数组的维护:
int get(int x){
if(fa[x]==x) return x;
int root=get(fa[x]);
d[x]+=d[fa[x]];
}

在这里插入图片描述
当接收到一个 C x y指令时,分别执行get(x)和get(y)完成查询和路径压缩。若二者返回值相同,则说明x和y处在同一列中。d[x]和
d[y]差的绝对值减1,就是x和y之间间隔的战舰数量。

当接收到一个M x y指令时,把x的树根作为y的树根的子节点,连接边的权值应该是合并之前y集合的大小(因为根据题意y集合中的全部战舰都排在x之前),还需要一个size数组在每个树根上记录集合的大小。

void merge(int x,int y){
x=get(x);y=get(y);
fa[x]=y;
d[x]=size[y];
size[y]+=size[x];
}

完整代码:


#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
using namespace std;
int n;
int p[30010], dis[30010], size[30010];
int find(int k)
{
 if (p[k] != k)
 {
  int f = find(p[k]);
  dis[k] += dis[p[k]];
  p[k] = f;
 }
 return p[k];
}
int main()
{
 for (int i = 1; i <= 30000; i++)
 {
  size[i] = 1;
  p[i] = i;
 }
 scanf("%d", &n);
 while (n--)
 {
  char s[2];
  int x, y;
  scanf("%s", s);
  scanf("%d%d", &x, &y);
  int fi = find(x), fj = find(y);
  if (s[0] == 'M')
  {
   p[fi] = fj;
   dis[fi] = size[fj];
   size[fj] += size[fi];
  }
  else
  {
   if (fi != fj)  printf("-1\n");
   else printf("%d\n", abs(dis[x] - dis[y]) - 1);
  }
 }
 return 0;
}
发布了9 篇原创文章 · 获赞 0 · 访问量 116

猜你喜欢

转载自blog.csdn.net/hwdn3000/article/details/104413614