并查集2——带权并查集

路径压缩

前面的并查集的复杂度实际上有些极端情况会很慢。比如树的结构正好是一条链,那么最坏情况下,每次查询的复杂度达到了 O(n)。

路径压缩 的思想是,我们只关心每个结点的父结点,而并不太关心树的真正的结构。

这样我们在一次查询的时候,可以把查询路径上的所有结点的 father[i] 都赋值成为根结点。只需要在我们之前的查询函数上面很小的改动。

int get(int x) {
    if (father[x] == x) { // x 结点就是根结点
        return x;
    }
    return father[x] = get(father[x]); // 返回父结点的根结点,并另当前结点父结点直接为根结点
}

下面图片是路径压缩前后的对比。

路径压缩在实际应用中效率很高,其一次查询复杂度平摊下来可以认为是一个常数。并且在实际应用中,我们基本都用带路径压缩的并查集。

带权并查集

所谓带权并查集,是指结点存有权值信息的并查集。并查集以森林的形式存在,而结点的权值,大多是记录该结点与祖先关系的信息。比如权值可以记录该结点到根结点的距离。

例题

在排队过程中,初始时,一人一列。一共有如下两种操作:

  • 合并:令其中的两个队列 A,BA,B 合并,也就是将队列 AA 排在队列 BB 的后面
  • 查询:询问某个人在其所在队列中排在第几位

例题解析

我们不妨设 size[] 为集合中的元素个数,dist[] 为元素到队首的距离,合并时,dist[A.root] 需要加上 size[B.root]

(每个元素到队首的距离应该是到根路径上所有点的 dist[] 求和)

size[B.root] 需要加上 size[A.root](每个元素所在集合的元素个数只需查询该集合中根的 size[x.root])

1) 初始化:

void init() {
    for(int i = 1; i <= n; i++)  {
        father[i] = i, dist[i] = 0, size[i] = 1;
    }
}

2) 查找:查找元素所在的集合,即根结点。

int get(int x) {
    if(father[x] == x) {
        return x;
    }
    int y = father[x];
    father[x] = get(y);
    dist[x] += dist[y];  // x 到根结点的距离等于 x 到之前父亲结点距离加上之前父亲结点到根结点的距离
    return father[x];
}

路径压缩的时候,不需考虑 size[],但 dist[] 需要更新成到整个集合根的距离。

3) 合并

将两个元素所在的集合合并为一个集合。

通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。

void merge(int a, int b) {
    a = get(a);
    b = get(b);
    if(a != b) {  // 判断两个元素是否属于同一集合
        father[a] = b;
        dist[a] = size[b];
        size[b] += size[a];
    }
}

通过小小的改动,我们就可以查询并查集这一森林中,每个元素到祖先的相关信息。

看个题目

我们可以很容易的统计俩张卡片是否在同一个队列中,用并查集就可以了。

关键是怎么计算,在一个队列中的俩个卡片之间卡片数目,只要维护一下每个卡片到队列头的卡片数目就好了。

在计算同一队列中的俩个卡片之间卡片数目,只要把俩个卡片到队列头的卡片数目做差就可以了。

#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
#include<memory.h>
#include<cmath>
#define INF 0x3f3f3f3f
using namespace std;
int father[30100];
int son[30100];
//树的深度
int sizes[30100];
//到根结点的距离
int dists[30100];
int n,m,p,k,a,b,c,x,y,ans=0;
void init(int n)
{
    for(int i=1;i<=n;i++)
    {
        father[i]=i;
        son[i]=i;
        sizes[i]=1;
        dists[i]=0;
    }
}
int get(int x)
{
    if(father[x]==x)
        return x;
    int y=father[x];
    father[x]=get(y);
    dists[x]+=dists[y];
    return father[x];
}
int get_son(int x)
{
    if(son[x]==x)
        return x;
    else return son[x]=get_son(son[x]);
}
void unite(int x,int y)
{
    x=get(x);
    y=get(y);
    if(x!=y)
    {
        father[x]=y;
        dists[x]=sizes[y];
        sizes[y]+=sizes[x];
    }
}
int same(int x,int y)
{
    return get(x)==get(y);
}
int main()
{
    cin>>n;
    init(30050);
    char c;
    for(int i=0;i<n;i++)
    {
        cin>>c>>a>>b;
        if(c==‘C‘)
        {
            if(!same(a,b)) cout<<-1<<endl;
            else cout<<abs(dists[a]-dists[b])-1<<endl;
        }
        else if(c==‘M‘)
        {
            unite(a,b);
        }
    }
    return 0;
}
时间: 2024-10-13 12:31:19

并查集2——带权并查集的相关文章

并查集模板 &amp;&amp; 带权并查集模板

不带权: 1 int f[50050]; 2 void init(void) 3 { 4 for(int i=1;i<=n;i++) 5 f[i]=i; 6 } 7 int fd(int x) 8 { 9 return f[x]==x?x:fd[x]=fd(f[x]); 10 } 11 int uion(int x,int y) 12 { 13 int fa=fd(x),fb=fd(y); 14 if(fa!=fb)f[fa]=fb; 15 } 带权: 1 int f[K],rl[K]; 2 3

带权并查集&amp;&amp;并查集

并查集 一般的并查集主要记录节点之间的链接关系,而没有其他的具体的信息,仅仅代表某个节点与其父节点之间存在联系,它多用来判断图的连通性 主要操作有: 初始化 把每个点所在集合初始化为其自身.通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N). void init(int n) { for(int i=1;i<=n;i++) { q[i]=i; } } 查找 查找元素所在的集合,即根节点. int find(int x) { int h=x; if(h

hdu3038(带权并查集)

题目链接: http://acm.split.hdu.edu.cn/showproblem.php?pid=3038 题意: n表示有一个长度为n的数组, 接下来有m行形如x, y, d的输入, 表示从第x,个元素到第y个元素的和为d(包括x, 和y), 问m行输入里面有几个是错误的(第一个输入是正确的); 思路: 很显然带权并查集咯,我们可以用距离的概念代替和的概念比较好理解一点,d表示x到y的和即x到y的距离; 可以用rank[x]表示x到其父亲节点的距离,  将正确的距离关系合并到并查集中

【POJ1182】 食物链 (带权并查集)

Description 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃C,C吃A. 现有N个动物,以1-N编号.每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种. 有人用两种说法对这N个动物所构成的食物链关系进行描述: 第一种说法是"1 X Y",表示X和Y是同类. 第二种说法是"2 X Y",表示X吃Y. 此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的.当一句话满足下列三条之

【poj 1988】Cube Stacking(图论--带权并查集 模版题)

题意:有N个方块,M个操作{“C x”:查询方块x上的方块数:“M x y”:移动方块x所在的整个方块堆到方块y所在的整个方块堆之上}.输出相应的答案. 解法:带权并查集.每堆方块作为一个集合,维护3个数组:fa[x]表示x方块所在堆的最顶部的方块:d[x]表示x方块所在堆的最底部的方块:f[x]表示x方块方块x上的方块数. 1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<

并查集练习2(带权并查集)

明天旅游去爬山逛庙玩,今天练一天然后早早睡觉啦~ poj1703 Find them, Catch them (带权并查集) 1 #include<cstdio> 2 const int N=1e5+1; 3 int f[N]; 4 int r[N];//表示与父节点的关系,0同类,1不同类 5 int n; 6 void init(){ 7 for(int i=1;i<=n;++i){ 8 f[i]=i; r[i]=0; 9 } 10 } 11 int fin(int x){ 12 i

poj1417 带权并查集+0/1背包

题意:有一个岛上住着一些神和魔,并且已知神和魔的数量,现在已知神总是说真话,魔总是说假话,有 n 个询问,问某个神或魔(身份未知),问题是问某个是神还是魔,根据他们的回答,问是否能够确定哪些是神哪些是魔. 对于这些问题,我们只需要发现,如果回答对方是魔,那么即可以判断出这两个不是同一种族,而如果回答对方是神,那么说明这两个是同一种族,那么就可以用带权并查集合并这些神和魔,然后记录两种分别多少个,这样当所有询问都处理完时我们就可以得到一系列的集合,每个集合分别有它的两个种族的人数,但是此时对于每个

Lightoj1009 Back to Underworld(带权并查集)

转载请注明出处: http://www.cnblogs.com/fraud/          ——by fraud Back to Underworld Time Limit:4000MS     Memory Limit:32768KB     64bit IO Format:%lld & %llu Description The Vampires and Lykans are fighting each other to death. The war has become so fierc

[NOIP摸你赛]Hzwer的陨石(带权并查集)

题目描述: 经过不懈的努力,Hzwer召唤了很多陨石.已知Hzwer的地图上共有n个区域,且一开始的时候第i个陨石掉在了第i个区域.有电力喷射背包的ndsf很自豪,他认为搬陨石很容易,所以他将一些区域的陨石全搬到了另外一些区域. 在ndsf愉快的搬运过程中,Hzwer想知道一些陨石的信息.对于Hzwer询问的每个陨石i,你必须告诉他,在当前这个时候,i号陨石在所在区域x.x区域共有的陨石数y.以及i号陨石被搬运的次数z. 输入描述: 输入的第一行是一个正整数T.表示有多少组输入数据. 接下来共有