博客
关于我
并查集+线段树合并: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/

    你可能感兴趣的文章
    Node.js安装与配置指南:轻松启航您的JavaScript服务器之旅
    查看>>
    Node.js安装及环境配置之Windows篇
    查看>>
    Node.js安装和入门 - 2行代码让你能够启动一个Server
    查看>>
    node.js安装方法
    查看>>
    Node.js官网无法正常访问时安装NodeJS的方法
    查看>>
    node.js模块、包
    查看>>
    node.js的express框架用法(一)
    查看>>
    Node.js的交互式解释器(REPL)
    查看>>
    Node.js的循环与异步问题
    查看>>
    Node.js高级编程:用Javascript构建可伸缩应用(1)1.1 介绍和安装-安装Node
    查看>>
    nodejs + socket.io 同时使用http 和 https
    查看>>
    NodeJS @kubernetes/client-node连接到kubernetes集群的方法
    查看>>
    NodeJS API简介
    查看>>
    Nodejs express 获取url参数,post参数的三种方式
    查看>>
    nodejs http小爬虫
    查看>>
    nodejs libararies
    查看>>
    nodejs npm常用命令
    查看>>
    nodejs npm常用命令
    查看>>
    Nodejs process.nextTick() 使用详解
    查看>>
    NodeJS yarn 或 npm如何切换淘宝或国外镜像源
    查看>>