博客
关于我
并查集+线段树合并:P3224-永无乡
阅读量:198 次
发布时间:2019-02-28

本文共 2561 字,大约阅读时间需要 8 分钟。

并查集与线段树结合解决集合中的第k大权值查询问题

问题描述

在处理一系列权值点的集合操作时,我们需要在合并两个集合时,能够快速查询其中的第k大权值。传统的并查集难以满足这一需求,而结合并查集与线段树的方法可以有效解决问题。

解决方案

我们采用并查集结构,每个集合对应一个线段树。这样,在合并两个集合时,可以直接将对应的线段树合并,确保在后续查询时能够快速找到第k大权值。

技术实现

并查集部分

  • 并查集结构:使用路径压缩和按秩合并的优化方法,确保每次操作的时间复杂度较低。
  • 父节点数组:用于记录每个节点的父节点,路径压缩能有效降低查找时间。
  • 代表数组:用于存储每个集合的代表节点,避免多次查找相同父节点。
  • 线段树部分

  • 线段树节点结构:每个节点存储左、右子树指针以及区间的权值和。
  • 构建线段树:从1到n构建线段树,初始化每个节点的和。
  • 合并操作:在合并两个集合时,同时合并对应的线段树,使得查询操作能够快速获取第k大权值。
  • 查询操作:使用线段树的特性,快速定位第k大权值。
  • 代码解析

    线段树代码

    #include 
    using namespace std;#define mid ((l + r) >> 1)const int maxn = 1e5 + 5;int sum[maxn < 5], ls[maxn < 5], rs[maxn < 5], rt[maxn], tot;void pushup(int t) { int tl = ls[t], tr = rs[t]; sum[t] = sum[tl] + sum[tr];}int add(int l, int r, int t, int p, int c) { int now = ++tot; ls[now] = ls[t]; rs[now] = rs[t]; if (l == r) { sum[now] += c; return now; } if (p <= mid) { ls[now] = add(l, mid, ls[now], p, c); } else { rs[now] = add(mid + 1, r, rs[now], p, c); } pushup(now); return now;}int ask(int l, int r, int t, int k) { if (l == r) return l; if (sum[ls[t]] >= k) { return ask(l, mid, ls[t], k); } else { return ask(mid + 1, r, rs[t], k - sum[ls[t]]); }}int mer(int a, int b, int l, int r) { if (!a) return b; if (!b) return a; if (l == r) { sum[a] += sum[b]; return a; } ls[a] = mer(ls[a], ls[b], l, mid); rs[a] = mer(rs[a], rs[b], mid + 1, r); pushup(a); return a;}

    并查集代码

    int a[maxn], f[maxn], n, m, id[maxn];int getf(int x) {    return f[x] == x ? x : f[x] = getf(f[x]);}bool dsu_mer(int a, int b) {    int fa = getf(a), fb = getf(b);    if (fa == fb) return false;    f[fb] = fa;    mer(rt[fa], rt[fb], 1, n);    return true;}

    主函数

    int main() {    scanf("%d%d", &n, &m);    for (int i = 1; i <= n; i++) {        scanf("%d", a + i);        id[a[i]] = i;        f[i] = i;        rt[i] = add(1, n, rt[0], a[i], 1);    }    for (int i = 1; i <= m; i++) {        int x, y;        scanf("%d%d", &x, &y);        dsu_mer(x, y);    }    char op[5];    int q;    scanf("%d", &q);    for (int i = 1; i <= q; i++) {        scanf("%s", op);        scanf("%d%d", &x, &y);        if (op[0] == 'Q') {            x = getf(x);            if (sum[rt[x]] < y) {                printf("-1\n");            } else {                printf("%d\n", id[ask(1, n, rt[x], y)]);            }        } else {            dsu_mer(x, y);        }    }    return 0;}

    应用示例

    • 输入处理:读取n和m,然后初始化权值数组a。
    • 线段树构建:为每个节点构建线段树,记录每个节点的代表节点id。
    • 并查集操作:处理m次合并操作。
    • 查询操作:处理q次查询,返回第k大权值或-1。

    通过上述方法,我们可以在并查集合并过程中,高效地查询集合中的第k大权值。

    转载地址:http://gadi.baihongyu.com/

    你可能感兴趣的文章
    php 代码改进
    查看>>
    php 代码混淆
    查看>>
    PHP 使用 $_SERVER['PHP_SELF'] 获取当前页面地址及其安全性问题
    查看>>
    Redis系列之如何避免缓存击穿
    查看>>
    php 内存分析
    查看>>
    PHP 函数名前面加&
    查看>>
    redis报错
    查看>>
    php 删除包含某一字符的数组元素
    查看>>
    Redis学习总结(19)——Redis 5种集群方式对比
    查看>>
    php 反射
    查看>>
    php 处理 大并发
    查看>>
    php 大文件上传
    查看>>
    php 子进程监听消息,swoole学习笔记之多线程端口监听问题记录 多进程epoll模式...
    查看>>
    PHP 学习笔记 (四)
    查看>>
    Redis入门概述
    查看>>
    php 实现Iterator 接口
    查看>>
    PHP 实现N阶矩阵相乘
    查看>>
    php 实现进制转换(二进制、八进制、十六进制)互相转换
    查看>>
    PHP 实现页面跳转的三种方式及详细解析
    查看>>
    php 将XML对象转化为数组
    查看>>