草庐IT

算法竞赛进阶指南0x41并查集

心坚石穿 2023-03-28 原文

并查集简介

并查集的两类操作:

  1. Get 查询任意一个元素是属于哪一个集合。
  2. Merge 把两个集合合并在一起。

基本思想:找到代表元。

注意有两种方法:

  • 使用一个固定的值(查询方便,但是在合并的时候需要修改大量的值,比较复杂)

  • 使用树形结构,这样合并的时候可以直接让一个叫另一个

    eg. f[root1] = root2




并查集的路径压缩以及按秩合并

路径压缩:在每一次进行合并的时候,顺便更改每一个节点的值。(均摊复杂度:\(O(logN)\)

按秩合并:每一次查询的均摊复杂度是\(O(logN)\)

如果两个一起使用,那么最终的复杂度是线性的。但是正常使用路径压缩就行。




使用并查集来维护具传递性的性质

仅仅维护具有传递性:

AcWing237. 程序自动分析


思路:

  • 一种方法是使用树的无向图来进行维护相等关系。(每一个块里面全部相等)
  • 再就是使用并查集来维护传递关系。
    • 注意:相等具有传递性,但是不相等不具备传递性。

代码:

#include <bits/stdc++.h>
using namespace std;
int fa[200010];
map<int , int >mp;
vector<pair<int, int> >v;
int get(int x)
{
    if(fa[x] == x) return x;
    return fa[x] = get(fa[x]);
}
void solve(int n)
{
    bool tag = true;
    v.clear();
    int cnt = 0;
    for(int i = 1; i <= 2*n; i++) fa[i] = i;
    mp.clear();
    int a, b, eq;
    for(int i = 1; i <= n; i++)
    {
        scanf("%d%d%d", &a, &b, &eq);
        if(mp.find(a) == mp.end())  mp[a] = ++cnt;
        if(mp.find(b) == mp.end())  mp[b] = ++cnt;
        if(eq == 0)
        {
            v.push_back(make_pair(mp[a], mp[b]));
        }
        else
        {
            fa[get(mp[a])] = get(mp[b]);
        }
    }
    for(vector<pair<int, int > >::iterator it = v.begin(); it != v.end(); it++)
    {
        pair<int, int>t = *it;
        if(get(t.first) == get(t.second))
        {
            tag = false;
            break;
        }
    }
    //for(int i = 1; i <= 2*n; i++)
    //{
    //    printf("%d\t%d", i, fa[])
    //}
    if(tag) puts("YES");
    else puts("NO");
}
int main()
{
    int T;
    cin >> T;
    while(T--) 
    {
        int n;
        scanf("%d", &n);
        solve(n);
    }
    return 0;
}



并查集的带权路径以及扩展域:

  • 可以在logN的复杂度内查询某一个节点(在“链”中)到根节点的距离
  • 但是也具有要求
    在合并的时候,必须是把一个集合全部按照原有的顺序合并到另一个集合的末尾。

这个时候,有两个数组 d 和 size 集合:

  1. 如果是根节点,那么就size里存有这一个集合元素的多少。
  2. 其他节点存放到父亲节点的带权路径长度d。

注意:仅仅在查询过后,d数组的内容才是到根节点的距离。




AcWing238. 银河英雄传说

代码

#include <bits/stdc++.h>
using namespace std;
int fa[30010];
int d[30010];
int s[30010];
int get(int x)
{
    if(x == fa[x]) return x;
    int root = get(fa[x]);
    d[x] += d[fa[x]];
    fa[x] = root;
    return root;
}
int merge(int a, int b)
{
    int x = get(a);
    int y = get(b);
    fa[x] = y;
    d[x] = s[y];
    s[y] += s[x];
}
int main()
{
    int T;
    cin >> T;
    for(int i = 0; i <= 30002; i++)
    {
        fa[i] = i;
        d[i] = 0;
        s[i] = 1;
    }
    while(T--)
    {
        char buf[12];
        int a, b;
        scanf("%s%d%d", buf, &a, &b);
        if(buf[0]=='M')
        {
            if(get(a) != get(b))//如果在一次合并之后,再次合并,那么就是把一个空的合并到了战舰末尾。
                merge(a, b);
        }
        else
        {
            int x = get(a);
            int y = get(b);
            if(x==y)
            {
                if (a==b) puts("0");//注意可能两次询问的是同一个战舰
                else printf("%d\n", abs(d[a]-d[b])-1);
            }
            else
                puts("-1");
        }
    }
    return 0;
}



239. 奇偶游戏

思路:

这道题目涉及到区间内的操作。
需要把区间的操作转化为端点的操作。
不妨假设有一个前缀数组,保存着从最开始的点到这一个位置1的个数。
现在进行一下转化:

  • 如果一个区间内有奇数个1,那么端点的奇偶性不同。
  • 如果一个区间内有偶数个1,那么端点奇偶性相同。

维护一种传递的关系: 使用并查集

注意:区间长度大,但是总体数目少,可以考虑离散化。

方法一:采用带边权来进行实现。

与上一题不同,对于一个抽象的并查集,把一个合并到另一个上时,边权是可以自己给定的。

浅浅地证明一下:当区间端点没有发生冲突,那么存在一种序列满足条件
(为了说明区间端点的冲突是造成判断说谎的充分必要条件)

#include <bits/stdc++.h>
using namespace std;
map<int , int> mp;
int fa[10010];
int d[10010];
//int s[10010];
int get(int x)
{
    if(x == fa[x]) return x;
    int root = get(fa[x]);
    d[x] = d[fa[x]] ^ d[x];
    return fa[x] = root;
}

bool merge(int a, int b, int mod)
{
    int x = get(a);
    int y = get(b);
    if(x==y)
    {
        if(d[a] ^ d[b] == mod)
            return true;
        else 
            return false;
    }
    fa[x] = y;
    d[x] = mod^d[a]^d[b];
    return true;
}
int main()
{
    for(int i = 0; i <= 10000; i++)
    {
        fa[i] = i;
        d[i] = 0;
    }
    int ans = INT_MAX;
    int cnt = 0;
    int N, M;
    cin >> N >> M;
    for(int i = 1; i <= M; i++)
    {
        int a, b;
        char buf[12];
        scanf("%d%d%s", &a, &b, buf);
        a--;
        if(mp.find(a) == mp.end()) mp[a] = ++cnt;
        if(mp.find(b) == mp.end()) mp[b] = ++cnt;
        bool tag;
        if(buf[0] == 'e')
            tag = merge(mp[a], mp[b], 0);
        else 
            tag = merge(mp[a], mp[b], 1);
        if(!tag)
        {
            ans = min(ans, i);
        }
    }
    if(ans == INT_MAX)
        printf("%d\n", M);
    else
        printf("%d", ans-1);
    return 0;
}

方法二:采用拓展域来进行求解:

还是有一种等价关系,只不过可能由这一个域推到另一个域上。所以,采用多个域来维护传递性。

思路

如果进行查询,没有发现矛盾,直接合并(因为如果本来在一起,合并也没有什么后果)

代码
#include <bits/stdc++.h>
using namespace std;
map<int , int> mp;
int fa[20010];
const int divv = 10002;
int get(int x)
{
    if(x== fa[x]) return x;
    return fa[x] = get(fa[x]);
}
bool merge(int x, int y, int mod)
{
    int x_odd = x;
    int x_even = x + divv;
    int y_odd = y;
    int y_even = y + divv;
    if(mod)//奇数
    {
        if(get(x_odd) == get(y_odd))
        {
            return false;
        }
        else
        {
            fa[get(x_odd)] = get(y_even);
            fa[get(y_odd)] = get(x_even);
        }
    }
    else
    {
        if(get(x_odd) == get(y_even))
            return false;
        else
        {
            fa[get(x_odd)] = get(y_odd);
            fa[get(x_even)] = get(y_even);
        }
    }
    return true;

}
int main()
{
    for(int i = 0; i <= 20009; i++)
    {
        fa[i] = i;
    }
    int ans = INT_MAX;
    int cnt = 0;
    int N, M;
    cin >> N >> M;
    for(int i = 1; i <= M; i++)
    {
        int a, b;
        char buf[12];
        scanf("%d%d%s", &a, &b, buf);
        a--;
        if(mp.find(a) == mp.end()) mp[a] = ++cnt;
        if(mp.find(b) == mp.end()) mp[b] = ++cnt;
        bool tag;
        if(buf[0] == 'e')
            tag = merge(mp[a], mp[b], 0);
        else 
            tag = merge(mp[a], mp[b], 1);
        if(!tag)
        {
            ans = min(ans, i);
        }
    }
    if(ans == INT_MAX)
        printf("%d\n", M);
    else
        printf("%d", ans-1);
    return 0;
}

AcWing240. 食物链


思路:这道题目是要动态地维护传递关系,所以考虑到并查集。
对于这个关系来说,看不出来传递性,所以要使用扩展域或者是边带权。

方法一:扩展域

#include <bits/stdc++.h>
using namespace std;
int fa[150012];
const int divv = 50000;
int get(int x)
{
    if(x == fa[x]) return x;
    return fa[x] = get(fa[x]);
}
bool merge(int tag, int a, int b)
{
    int a1 = a, a2 = a+divv, a3 = a2+divv;
    int b1 = b, b2 = b+divv, b3 = b2 + divv;
    if(tag==1)//表示a与b是同类
    {
        if(get(a1)==get(b2) || get(b1)==get(a2))
            return false;
        fa[get(a1)] = get(b1);
        fa[get(a2)] = get(b2);
        fa[get(a3)] = get(b3);
    }
    else//表示a吃b
    {
        if(get(a1)==get(b1) || get(b1) == get(a2))
            return false;
        fa[get(a1)] = get(b2);
        fa[get(a2)] = get(b3);
        fa[get(a3)] = get(b1);
    }
    return true;
}
int main()
{
    for(int i = 1; i <= 150010; i++)
    {
        fa[i] = i;
    }
    int cnt = 0;
    bool right = true;
    int T, K;
    cin >> T >> K;
    for(int i = 1; i <= K; i++)
    {
        
        int tag, a, b;
        scanf("%d%d%d", &tag, &a, &b);
        if(a > T || b > T) //针对第二条判断真假
        {
            cnt ++;
            right = false;
        }
        else if(!merge(tag, a, b))//注意:这个必须是else,因为如果
        //上面一旦不合法,那么就不能进行下面的操作
        {
            cnt++;
            right = false;
        }
    }
    printf("%d\n", cnt);
    return 0;
}

有关算法竞赛进阶指南0x41并查集的更多相关文章

  1. 电脑0x0000001A蓝屏错误怎么U盘重装系统教学 - 2

      电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。  准备工作:  1、U盘一个(尽量使用8G以上的U盘)。  2、一台正常联网可使用的电脑。  3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。  4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。  U盘启动盘制作步骤:  注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注

  2. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  3. Ruby 和指南针路径与 yeoman 项目 - 2

    我安装了ruby​​、yeoman,当我运行我的项目时,出现了这个错误:Warning:Running"compass:dist"(compass)taskWarning:YouneedtohaveRubyandCompassinstalledthistasktowork.Moreinfo:https://github.com/gruUse--forcetocontinue.Use--forcetocontinue.我有进入可变session目标的路径,但它不起作用。谁能帮帮我? 最佳答案 我必须运行这个:geminstallcom

  4. 100个python算法超详细讲解:画直线 - 2

    1.问题描述使用Python的turtle(海龟绘图)模块提供的函数绘制直线。2.问题分析一幅复杂的图形通常都可以由点、直线、三角形、矩形、平行四边形、圆、椭圆和圆弧等基本图形组成。其中的三角形、矩形、平行四边形又可以由直线组成,而直线又是由两个点确定的。我们使用Python的turtle模块所提供的函数来绘制直线。在使用之前我们先介绍一下turtle模块的相关知识点。turtle模块提供面向对象和面向过程两种形式的海龟绘图基本组件。面向对象的接口类如下:1)TurtleScreen类:定义图形窗口作为绘图海龟的运动场。它的构造器需要一个tkinter.Canvas或ScrolledCanva

  5. Simulink方法总结和避坑指南(一)——Simulink入门与基本调试方法 - 2

    文章目录一、项目场景二、基本模块原理与调试方法分析——信源部分:三、信号处理部分和显示部分:四、基本的通信链路搭建:四、特殊模块:interpretedMATLABfunction:五、总结和坑点提醒一、项目场景  最近一个任务是使用simulink搭建一个MIMO串扰消除的链路,并用实际收到的数据进行测试,在搭建的过程中也遇到了不少的问题(当然这比vivado里面的debug好不知道多少倍)。准备趁着这个机会,先以一个很基本的通信链路对simulink基础和相关的debug方法进行总结。  在本篇中,主要记录simulink的基本原理和基本的SISO通信传输链路(QPSK方式),计划在下篇记

  6. ruby-on-rails - 如何解决#<Book::ActiveRecord_Relation:0x007fb709a6a8c0> 的未定义方法 `to_key'? - 2

    我遇到了未定义方法`to_key'的问题这是我的books_controller.rbclassBooksController和我的索引页如下。index.html.erb......现在当我要访问索引页面时出现如下错误。undefinedmethod`to_key'for# 最佳答案 index通常返回一个集合。事实上,您的Controller符合要求。但是,您的View试图为其定义一个表单。正如您所发现的,这不会成功。表单适用于实体,而不适用于集合。该错误在您看来以及您希望如何处理index。

  7. ruby - Ruby gems 的问题(损坏?)试图让指南针在 npm 中工作 - 2

    我不是Ruby专家,但想弄清楚发生了什么,因为我试图让指南针在节点应用程序中工作,但我的Ruby似乎坏了。打字:ruby--version让我:ruby2.1.1p76(2014-02-24revision45161)[x86_64-darwin13.0]我安装了Homebrew,之前遇到过Ruby版本的问题,但它似乎已安装并且可以正常工作。但是,当我使用gem输入请求时,出现此错误:$gem-hErrorloadingRubyGemsplugin"/Users/user_dir/.rvm/gems/ruby-2.1.1@global/gems/executable-hooks-1.3

  8. ruby - 在 Ruby 中实现 Luhn 算法 - 2

    我一直在尝试用Ruby实现Luhn算法。我一直在执行以下步骤:该公式根据其包含的校验位验证数字,该校验位通常附加到部分帐号以生成完整帐号。此帐号必须通过以下测试:从最右边的校验位开始向左移动,每第二个数字的值加倍。将乘积的数字(例如,10=1+0=1、14=1+4=5)与原始数字的未加倍数字相加。如果总模10等于0(如果总和以零结尾),则根据Luhn公式该数字有效;否则无效。http://en.wikipedia.org/wiki/Luhn_algorithm这是我想出的:defvalidCreditCard(cardNumber)sum=0nums=cardNumber.to_s.s

  9. Ruby 斐波那契算法 - 2

    下面是我写的一个计算斐波那契数列中的值的方法:deffib(n)ifn==0return0endifn==1return1endifn>=2returnfib(n-1)+(fib(n-2))endend它工作到n=14,但在那之后我收到一条消息说程序响应时间太长(我正在使用repl.it)。有人知道为什么会这样吗? 最佳答案 Naivefibonacci进行了大量的重复计算-在fib(14)fib(4)中计算了很多次。您可以将内存添加到您的算法中以使其更快:deffib(n,memo={})ifn==0||n==1returnnen

  10. ruby-on-rails - Rspec - Controller 测试错误 - Paperclip::AdapterRegistry::NoHandlerError: 找不到 "#<File:0x531beb0>"的处理程序 - 2

    我如下询问了我的Rspec测试。Rspec-RuntimeError:Calledidfornil,whichwouldmistakenlybe4在相同的代码上(“items_controller.rb”的Rspec测试),我试图对“PUTupdate”进行测试。但是我收到错误消息“Paperclip::AdapterRegistry::NoHandlerError:找不到“#”的处理程序。我的Rspec测试如下。老实说,我猜这次失败的原因是“let(:valid_attributes)”上的“photo”=>File.new(Rails.root+'app/assets/images

随机推荐