`
jackchen0227
  • 浏览: 146857 次
  • 性别: Icon_minigender_1
  • 来自: 帝都
社区版块
存档分类
最新评论

【zz】并查集

阅读更多

http://blog.sina.com.cn/s/blog_4c396f430100cort.html

嗯……最近好好学了下并查集……以弥补我远不过关的数据结构……(其实学了并查集我的数据结构还是远不过关……)

首先要说的是……我现在才学会的东西,逆铭大牛牛早在几年前就学会了……大家可以参考他的博客 ……

那么,并查集是一种对不相交集合的数据结构,它支持两种操作:

  • 合并两个集合
  • 查找一个元素属于哪个集合

通常并查集可以用森林或者是链表来实现,但是通常链表实现效率不如森林实现,所以通常使用并查集的森林实现,这时,每个集合其实就是一棵树,并且这个集合就用这颗树的树根来标识。

可以用数组来表示森林,用p[i]表示结点i的父结点。初始的时候让每个结点的父结点指向其自身,即p[i] = i,每个结点就是一个单独的集合。那么合并两个元素x,y所在的集合,就可以先把x和y所在集合的标识,即其树根计算出来,设为p_x,p_y,然后再让p_y的父结点指向p_x即可,即p[p_y] = p_x。下面的函数合并了两个元素x,y所在的集合:

void union_set(int x, int y) {
    int p_x = get_parent (x), p_y = get_parent(y);
    p[p_y] = p_x;
}

 在查找一个元素属于哪个集合,即查找这个元素的根结点的时候,如果这个元素的父结点就是其自身,即这个元素本身就是根结点,那么查找结束,返回其本身。否则返回这个元素父结点的根结点。这里有一个称为路径压缩的优化,就是在查找这个元素的根结点时,把其到根结点的路径上的所有点都直接连接到根结点上,可以防止树退化成链。所以可以用递归来实现(不用递归效率更高,读者可以尝试自行完成……)。下面的函数返回结点k的根结点,同时进行路径压缩:

int get_parent(int k) {
    return p[k] == k ? k : (p[k] = get_parent(p[k]));
}

 还有一个优化叫做按秩合并,就是总是在合并两个集合的时候总是把深度小的树合并到深度大的树里面,以防止树退化成链。这里我没有提供代码(其实代码也相当简单),具体请大家参考CLRS……

综上,并查集的实现可以是如下的代码:

struct uf_set {
    int p[0xffff];
    void clear() {
        for (int i = 0; i < 0xffff; ++i)
            p[i] = i;
    }
    int get_parent(int k) {
        return p[k] == k ? k : (p[k] = get_parent(p[k]));
    }
    void union_set(int x, int y) {
        int p_x = get_parent(x), p_y = get_parent(y);
        p[p_y] = p_x;
    }
};

有关并查集更详细的内容请参考CLRS或者黑书……下面说三道题目:

这个题没什么说的了,就是最裸的并查集,统计与0属于同一个集合的结点数即可。

代码如下:

#include <cstdio>

using namespace std;

struct uf_set {
    int p[30000];
    void clear() {
        for (int i = 0; i < 30000; ++i)
            p[i] = i;
    }
    int get_parent(int k) {
        return p[k] == k ? k : (p[k] = get_parent(p[k]));
    }
    void union_set(int x, int y) {
        int p_x = get_parent(x), p_y = get_parent(y);
        p[p_y] = p_x;
    }
};

int n, m, ans;
uf_set union_find_set;

int main() {
    for (scanf("%d%d", &n, &m); n != 0; scanf("%d%d", &n, &m)) {
        union_find_set.clear();
        for (int i = 0, num, id1, id2; i < m; ++i) {
            scanf("%d%d", &num, &id1);
            for (int j = 1; j < num; ++j) {
                scanf("%d", &id2);
                union_find_set.union_set(id1, id2);
            }
        }
        int group0 = union_find_set.get_parent(0);
        ans = 0;
        for (int i = 0; i < n; ++i)
            if (union_find_set.get_parent(i) == group0)
                ++ans;
        printf("%d\n", ans);
    }
    return 0;
}

这个题是并查集的扩展。可以这样考虑:如果[s, t]这个区间内数的和为even,那么表示[0, s - 1]和[0, t](简记为s - 1和t)是奇偶性相同的,反之就是奇偶性不同的。但是我们在处理这个问题的时候没有必要像处理一般并查集问题一样把奇偶性相同的合并,把奇偶性不同的处理 为不同的集合。而是把所有已经发生了关系的集合都统统合并,并为每个元素都增加一个域p_r[i]表示结点i与其父亲结点(p[i])的关系是 p_r[i],p_r[i] = 1表示其与父结点的奇偶性相同,p_r[i] = 0表示奇偶性不同。那么只需要稍微思考一下在合并集合的时候(有两种:一种是奇偶性相同的集合合并,一种是奇偶性不同的集合合并)怎么维护p_r域就可以 了。还需要注意的是因为lenth太大(最大为1000000000),而questions and answers比较小(最大为5000),所以需要在读入数据以后进行离散化(当然是离散化三部曲:sort、unique、lower_bound 了!)。具体的实现细节请参考代码。

代码如下(URAL的):

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

struct question {
    int s, t;
    char f;
};

struct uf_set {
    int p[10000], p_r[10000];
    void clear() {
        for (int i = 0; i < 10000; ++i) {
            p[i] = i;
            p_r[i] = 1;
        }
    }
    int get_parent(int k) {
        if (p[k] == k)
            return k;
        int tmp = get_parent(p[k]);
        p_r[k] = 1 ^ p_r[p[k]] ^ p_r[k];
        return p[k] = tmp;
    }
    bool set_same(int x, int y) {
        int p_x = get_parent(x), p_y = get_parent(y);
        if (p_x == p_y)
            return p_r[x] == p_r[y];
        p[p_y] = p_x;
        p_r[p_y] = 1 ^ p_r[x] ^ p_r[y];
        return true;
    }
    bool set_diff(int x, int y) {
        int p_x = get_parent(x), p_y = get_parent(y);
        if (p_x == p_y)
            return p_r[x] != p_r[y];
        p[p_y] = p_x;
        p_r[p_y] = p_r[x] ^ p_r[y];
        return true;
    }
};

int l, n, num[10000], ans;
question q[5000];
uf_set union_find_set;

int main() {
    char buf[10];
    for (scanf("%d", &l); l != -1; scanf("%d", &l)) {
        scanf("%d", &n);
        for (int i = 0; i < n; ++i) {
            scanf("%d%d%s", &q[i].s, &q[i].t, buf);
            --q[i].s;
            q[i].f = buf[0];
            num[i * 2] = q[i].s;
            num[i * 2 + 1] = q[i].t;
        }
        sort(num, num + n * 2);
        l = unique(num, num + n * 2) - num;
        for (int i = 0; i < n; ++i) {
            q[i].s = lower_bound(num, num + l, q[i].s) - num;
            q[i].t = lower_bound(num, num + l, q[i].t) - num;
        }
        union_find_set.clear();
        for (ans = 0; ans < n; ++ans) {
            if (q[ans].f == 'e') {
                if (!union_find_set.set_same(q[ans].s, q[ans].t))
                    break;
            } else if (q[ans].f == 'o') {
                if (!union_find_set.set_diff(q[ans].s, q[ans].t))
                    break;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

同样是并查集的扩展,和上一题目差不多,只不过p_r[i]有三种取值了:p_r[i] = 0表示种类相同,p_r[i] = 1表示i吃i的父结点,p_r[i] = -1表示i被i的父结点吃。只需要搞清楚合并集合以后p_r的取值就可以了。

代码如下:

#include <cstdio>
#include <cstring>

using namespace std;

struct uf_set {
    int p[50000], p_r[50000];
    void clear() {
        for (int i = 0; i < 50000; ++i) {
            p[i] = i;
            p_r[i] = 0;
        }
    }
    int get_relation(int x, int y) {
        if (x == -1 && y == -1)
            return 1;
        if (x == 1 && y == 1)
            return -1;
        return x + y;
    }
    int get_parent(int k) {
        if (p[k] == k)
            return k;
        int tmp = get_parent(p[k]);
        p_r[k] = get_relation(p_r[k], p_r[p[k]]);
        return p[k] = tmp;
    }
    bool set_same(int x, int y) {
        int p_x = get_parent(x), p_y = get_parent(y);
        if (p_x == p_y)
            return p_r[x] == p_r[y];
        p_r[p_y] = get_relation(-p_r[y], p_r[x]);
        p[p_y] = p_x;
        return true;
    }
    bool set_eat(int x, int y) {
        int p_x = get_parent(x), p_y = get_parent(y);
        if (p_x == p_y)
            return get_relation(p_r[x], -p_r[y]) == 1;
        p_r[p_y] = get_relation(-p_r[y], get_relation(-1, p_r[x]));
        p[p_y] = p_x;
        return true;
    }
};

int n, m, ans;
uf_set union_find_set;

int main() {
    scanf("%d%d", &n, &m);
    union_find_set.clear();
    for (int i = 0, d, x, y; i < m; ++i) {
        scanf("%d%d%d", &d, &x, &y);
        if (x > n || y > n) {
            ++ans;
            continue;
        }
        if (d == 1) {
            if (!union_find_set.set_same(x - 1, y - 1))
                ++ans;
        } else if (d == 2) {
            if (!union_find_set.set_eat(x - 1, y - 1))
                ++ans;
        }
    }
    printf("%d\n", ans);
    return 0;
}

发现写ACM相关的东西好累的说……希望对大家有用……

分享到:
评论

相关推荐

    JSF架构图zz

    JSF框架提供了一种易于使用的API来构建动态Web应用程序,并且通过其丰富的特性集,如表单处理、验证、国际化支持等,使得开发者能够快速地创建出功能完备的Web应用。 #### 二、JSF架构组成 根据给定的内容,“JSF...

    VBA数据库查询及数据自动导出多Excel报表

    ' 处理记录集 End With ``` - **Open 方法**: 用于打开 Recordset 对象,参数包括 SQL 查询语句、Connection 对象等。 - **CommandTimeout**: 设置命令执行的超时时间,单位为秒。 ### 将查询结果自动导出到多个 ...

    BDD-ZZ1:ZZ1上的TD等基础知识-SQL

    2. **并行处理**:Teradata的设计充分利用了并行计算,允许同时处理多个查询,使得处理大数据集时的性能显著优于传统的单线程数据库。 3. **DML操作**:在Teradata中,对数据的插入、更新和删除操作需要考虑到其...

    指标平台加速零售数字化转型zz.pptx

    为了解决上述挑战,Kyligence Zen 应运而生,它是一款集业务模型、指标管理、指标加工、数据服务于一体的一站式指标平台。该平台旨在通过提供高效的数据处理和分析能力,帮助零售企业实现从数据驱动到指标驱动的转变...

    VivaNavy.9fbfsplbf6.cf4n4ZZ

    标题“VivaNavy.9fbfsplbf6.cf4n4ZZ”和描述中并未直接提供具体的IT知识点,但考虑到标签是“HTML”,我们可以推测这个压缩包可能包含与HTML(超文本标记语言)相关的学习资源或项目。HTML是网页开发的基础,用于...

    算法数据结构学习视频教程,算法和数据结构的基础概念、进阶技巧以及特定算法的应用和实现

    第16节 并查集及其相关题目.mp4 第17节 图.mp4 第18节 认识一些经典递归过程.mp4 第19节 暴力递归到动态规划(一).mp4 第20节 暴力递归到动态规划(二).mp4 第21节 暴力递归到动态规划(三).mp4 第22节 暴力递归...

    py源码实例实例名言查询

    这可能包括连接数据库、执行SQL查询语句、处理结果集等操作。 4. 网络编程:如果名言数据是在线可访问的,可能会使用Python的网络库(如requests或urllib)来发送网络请求并获取数据。 5. 数据处理:获取到名言...

    AT指令,短信猫串口调试

    AT指令是串行通信中的一种标准命令集,主要用于配置和控制GSM/GPRS模块或“短信猫”,这些设备常用于实现远程通信中的短信收发功能。在VB(Visual Basic)编程环境中,我们可以创建应用程序来与短信猫进行交互,实现...

    bom.rar_BOM_storedprocedure_物料

    "zz_sp_BomSumExport"可能是一个用于汇总物料BOM数据并导出的特定功能,例如,它可能用于生成物料清单报告,展示产品及其所有组成部分,包括数量、层次结构和成本信息。 标签“bom”、“storedprocedure”和“物料...

    数学建模数据集全国银行名称及所在地数据mysql/sql

    1. 数学建模与数据集:数学建模是利用数学的方法和工具来解决实际问题的一种方法论,它是将现实世界中的问题抽象、简化并构建数学模型,然后通过数学推导、计算机模拟等方式求解模型,最终对现实问题给出定量的解答...

    实例MATLAB实现学生成绩查询系统源代码程序

    它集数值分析、矩阵运算、信号处理和图形显示于一体,被广泛应用于工程计算、控制设计、信号处理和通信等领域。MATLAB提供了一个交互式的环境,允许用户通过编写脚本或函数来快速实现复杂算法。它的另一个特点是拥有...

    中航信Eterm协议解析

    例如,Eterm协议可能定义了一套特定的命令格式,如"XX YY ZZ",其中"XX"代表命令类型,"YY"表示操作码,"ZZ"可能包含附加参数。在实际应用中,代理人可能输入如“CA QS 20220701”这样的指令来查询某一天CA航空公司...

    数学建模数据集外商直接投资数据大总结30年世界数据

    为了使用这些数据,用户需要有百度网盘账号并登录后,通过提供的链接下载数据集文件。 这份数据集的标签显示其可以用于数据科学、统计分析、商业分析、金融分析等领域的教学和研究。同时,数据集的特性表明它适用于...

    5152单片机proteus仿真和源码用定时器T0查询方式P2口8位控制LED闪烁

    5152单片机属于8051系列的一种扩展型号,其内部结构和指令集与传统的8051兼容。它具有更多的RAM空间以及更多的I/O口线,适用于需要较大存储空间和更多外部接口的应用场合。5152单片机的主要特点包括: 1. **内部...

    广工 数据库 考题(2)

    - 可以通过连接`SB`和`ZZ`表,并使用`INNER JOIN`或子查询的方式找到匹配的记录。 4. **查询出大修过的设备中每种设备大修费用的平均值**: - 通过聚合函数`AVG()`计算费用的平均值,并使用`GROUP BY`子句对不同...

    vim常用命令vim常用命令vim常用命令

    - `ZZ`: 保存当前文档并退出VIM。 5. **编辑操作**: - `dw`: 删除一个单词,需要先将光标移动到单词开头。 - `daw`: 删除光标所在单词。 - `dnw`: 删除n个单词。 - `dne`: 删除到下一个单词末尾。 - `dnl`: ...

    算法文档无代码一些与树有关的题目

    - 并查集(Disjoint Set Union, DSU)的实现和应用 这些知识点都是算法设计和数据结构领域中处理树型结构时可能需要掌握的关键点。不同类型的树结构适用于解决不同类型的算法问题,例如二叉搜索树适用于快速查找和...

    java调用Oracle存储过程

    另外,如果存储过程返回游标(结果集),可以通过`ResultSet`接口进行处理。记得检查存储过程的返回值,它通常是一个表示成功与否的状态。 在开发过程中,可以使用诸如IntelliJ IDEA或Eclipse这样的IDE进行调试,...

    文档数据结构之最小生成树Prim算法

    - **数据结构需求不同**:Prim算法通常需要维护一个优先队列来更新顶点的最小距离,而Kruskal算法需要使用并查集来避免形成环路。 - **应用场景差异**:两者都可以应用于求解最小生成树问题,但在某些特定情况下,一...

Global site tag (gtag.js) - Google Analytics