• 【数据结构】带权并查集


    百度百科

    没有带权并查集的百科

    注:感谢@DDYYZZ julao与我的讨论

    Definition&Solution

      对于普通并查集,我们维护f[a]为a的父节点。但是在一些对父子关系有严格要求的问题中,我们并不能简单的通过f[a]=find(f[a])将a的父亲直接指向a的祖先。似乎解决办法只有不进行路径压缩。但是需要注意的是,不进行路径压缩的复杂度为O(mn),其中m为操作个数,n为最大节点个数。对于一般的题目都无法承受。换一种思路,我们使用deepth[i]点i距离i的根节点的距离。这样,我们就可以一边进行路径压缩一边保留节点间的关系了。

      要深入理解带权并查集的操作流程,首先需要深入了解普通路径压缩并查集是怎么工作的。例如,有如下两个集合:

    现在需要合并两个集合。合并后结果如图:

    现在假如我们要合并3和另一节点,我们调用find(3)。其中find函数写法如下

    int find(int x) {
        return f[x]==x?x:f[x]=find(f[x]);
    }

    这句话翻译后,是将自己节点的父亲接到父节点的父亲上。合并后效果如图:

    可以发现,真正对树形造成更改从而保证复杂度的,是find操作,而不是合并操作。在一次合并的时候,被合并节点的子树上的节点的信息并不是合法的(即不满足路压并查集的要求)。但是每次调用find函数时,通过父节点的信息更新子节点的信息,从而保证从被调用的节点到根节点的链上是合法的。由于根节点显然是合法的,整个过程的合法性可以通过数学归纳证明。

    现在考虑最基本的带权并查集:对于两个已知链,将一条链的顶端合并到另一条链的尾部。询问链上两点间的距离。

    我们不妨用deepth[i]代表i到i的父节点在链上的实际距离。考虑合并两条新链的时候,我们维护一个参数size[i]代表i所在链的长度。不妨设以i为头的链连接到以j为头的链的尾部,那么显然有deepth[i]=size[j],f[i]=j。

    显然j原有的链上节点的信息是合法的,但是原来i链上的信息全部是不合法的。如何解决呢?考虑在find函数中通过父节点更新子节点的合法信息。当f[i]合法时,我们通过路径压缩将i点连接到f[i]的父节点上,归纳法易证i点会被连接到根节点上。(在这个问题中,根节点就是该链顶端的节点标号)那么deepth[i]应该记录i到根节点的实际距离。但这时deepth[i]存储的是i到f[i]的实际距离。但是注意到在归纳时i的父节点的信息已经是合法的了,那么deepth[f[i]]就应该等于f[i]到根节点的距离。显然,deepth[i]=deepth[i]+deepth[f[i]]。在等号右边,deepth[i]存储的是i到f[i]的实际距离。更新后,deepth[i]被更新为到根节点(顶端)的距离了。这样就可以保证我们用到的每个点,他们的信息都是合法的。需要注意的是,凡是经过路压的点,最终都会形成根-子孙的两层结构。由于我们进行了路径压缩,deepth[i]的合法值就是i到根节点的的距离。那么每次询问的答案就是abs(deepth[i]-deepth[j])+1了。

    下面是find函数的写法:

    #define ci const int
    int find(ci x) {
        if(frog[x]==x) return x;
        int k=frog[x];frog[x]=find(frog[x]);
        deepth[x]+=deepth[k];
        return frog[x];
    }

    以及合并函数

    inline void cont(ci x,ci y) {
        int fa=find(x),fb=find(y);
        if(fa==fb) return;
        frog[fa]=fb;deepth[fa]=sz[fb];sz[fb]+=sz[fa];sz[fa]=sz[fb];
    }

    Example

    lgP1196 [NOI2002]银河英雄传说

    Description

    杨威利擅长排兵布阵,巧妙运用各种战术屡次以少胜多,难免恣生骄气。在这次决战中,他将巴米利恩星域战场划分成 3000030000 列,每列依次编号为 1, 2, …,300001,2,,30000 。之后,他把自己的战舰也依次编号为 1, 2, …, 300001,2,,30000 ,让第 ii号战舰处于第 ii 列 (i = 1, 2, …, 30000)(i=1,2,,30000) ,形成“一字长蛇阵”,诱敌深入。这是初始阵形。当进犯之敌到达时,杨威利会多次发布合并指令,将大部分战舰集中在某几列上,实施密集攻击。合并指令为 M_{i,j}Mi,j ,含义为第i号战舰所在的整个战舰队列,作为一个整体(头在前尾在后)接至第j号战舰所在的战舰队列的尾部。显然战舰队列是由处于同一列的一个或多个战舰组成的。合并指令的执行结果会使队列增大。

    然而,老谋深算的莱因哈特早已在战略上取得了主动。在交战中,他可以通过庞大的情报网络随时监听杨威利的舰队调动指令。

    在杨威利发布指令调动舰队的同时,莱因哈特为了及时了解当前杨威利的战舰分布情况,也会发出一些询问指令: C_{i,j}Ci,j 。该指令意思是,询问电脑,杨威利的第 ii 号战舰与第 jj 号战舰当前是否在同一列中,如果在同一列中,那么它们之间布置有多少战舰。

    Input

    第一行有一个整数 T(1 le T le 500,000)T(1T500,000) ,表示总共有 TT 条指令。

    以下有 TT 行,每行有一条指令。指令有两种格式:

    1. M_{i,j}Mi,j : ii 和 jj 是两个整数 (1 le i,j le 30000)(1i,j30000) ,表示指令涉及的战舰编号。该指令是莱因哈特窃听到的杨威利发布的舰队调动指令,并且保证第 ii 号战舰与第 jj 号战舰不在同一列。

    2. C_{i,j}Ci,j : ii 和 jj 是两个整数 (1 le i,j le 30000)(1i,j30000) ,表示指令涉及的战舰编号。该指令是莱因哈特发布的询问指令。

    Output

    依次对输入的每一条指令进行分析和处理:

    如果是杨威利发布的舰队调动指令,则表示舰队排列发生了变化,你的程序要注意到这一点,但是不要输出任何信息;

    如果是莱因哈特发布的询问指令,你的程序要输出一行,仅包含一个整数,表示在同一列上,第 ii 号战舰与第 jj 号战舰之间布置的战舰数目。如果第 ii 号战舰与第 jj 号战舰当前不在同一列上,则输出 -11 。

    Sample Input

    4
    M 2 3
    C 1 2
    M 2 4
    C 4 2

    Sample Output

    -1
    1

    Hint

    (1T500,000)  

    (1 le i,j le 30000)(1i,j30000)

    Solution

    模板题

    Code

    #include<cstdio>
    #define maxn 30010
    #define ci const int
    
    inline void qr(int &x) {
        char ch=getchar(),lst=NULL;
        while(ch>'9'||ch<'0') lst=ch,ch=getchar();
        while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
        if(lst=='-') x=-x;
    }
    
    template <typename T>
    inline T mmax(const T &a,const T &b) {if(a>b) return a;return b;}
    template <typename T>
    inline T mmin(const T &a,const T &b) {if(a<b) return a;return b;}
    template <typename T>
    inline T mabs(const T &a) {if(a>=0) return a;return -a;}
    
    template <typename T>
    inline void mswap(T &a,T &b) {T temp=a;a=b;b=temp;}
    
    int t,a,b;
    int frog[maxn],deepth[maxn],sz[maxn];
    
    int find(ci);
    int ask(ci,ci);
    void cont(ci,ci);
    
    int main() {
        qr(t);
        for(int i=1;i<=30000;++i) frog[i]=i,sz[i]=1;
        while(t--) {
            char ch=getchar();while(ch!='M'&&ch!='C') ch=getchar();
            a=b=0;qr(a);qr(b);
            if(ch=='M') cont(a,b);else printf("%d
    ",ask(a,b));
        }
        return 0;
    }
    
    int find(ci x) {
        if(frog[x]==x) return x;
        int k=frog[x];frog[x]=find(frog[x]);
        deepth[x]+=deepth[k];
        return frog[x];
    }
    
    inline void cont(ci x,ci y) {
        int fa=find(x),fb=find(y);
        if(fa==fb) return;
        frog[fa]=fb;deepth[fa]=sz[fb];sz[fb]+=sz[fa];sz[fa]=sz[fb];
    }
    
    inline int ask(ci x,ci y) {
        int fa=find(x),fb=find(y);
        if(fa!=fb) return -1;
        return mabs(deepth[x]-deepth[y])-1;
    }

    300

  • 相关阅读:
    CSS 内外边距
    CSS 边框
    android chrome this account already exists on your device
    linux s s r client ubuntu kali
    rEFInd 美化 windows
    sogou opensuse
    Ping 虚拟机 超时
    python3 批量自动下载对应用户 github上的项目 或者 starts 的项目
    materialize 样例
    opensuse input methods
  • 原文地址:https://www.cnblogs.com/yifusuyi/p/9404672.html
Copyright © 2020-2023  润新知