当前位置: 首页 > news >正文

信用网站系统建设方案阿里云服务器建设网站选择那个镜像

信用网站系统建设方案,阿里云服务器建设网站选择那个镜像,北京漫步云端网站建设,莆田做网站的公司对于并查集这个算法一直一知半解,似懂非懂…… 今天正好碰到一个相关的题目,就拿出来研究一下,顺带再深入理解一下并查集这个算法题目分析 已知条件1:小苯来到一座山脉,山脉中有n座山,每座山有一个高度&…
对于并查集这个算法一直一知半解,似懂非懂……
今天正好碰到一个相关的题目,就拿出来研究一下,顺带再深入理解一下并查集这个算法

题目描述

题目分析

已知条件1:小苯来到一座山脉,山脉中有n座山,每座山有一个高度,山与山之间有路径相连……

从这个条件我们大概可以知道这道题是要考察图论的算法,这里的每座山可以抽象成一个节点,山与山的路径可以抽象成节点的边

已知条件2:a号山和b号山之间如果有路径的话,可以从a走到b,也可以从b走到a……

从这个条件来看,它的意思是想告诉我们节点间的边没有方向,这是个无向图

已知条件3:如果两座山之间的高度差大于k,那么就不会走这条路,然后他提出了q次查询,如果高度差大于k的路他不走,能够找到一条路从a山到b山呢?

即如果两个节点的差值大于k,那么就把这条边删除,这样删完之后,从节点a到节点b是否还有连通的路径,是则返回YES,不是则返回NO。

这样把题目条件拆解开,突然发现题目就一目了然了!把题目分析清楚,再找对应的解题方法就简单多了!

这就是图论中一个经典的离线查询问题!

关于图论中的离线查询问题

在图论中,离线查询问题指的是:所有查询在处理开始前就已全部给出,我们可以先对查询和图的信息进行预处理(如排序、重组织等),再批量处理所有查询,从而获得更高的效率

与之相对的是在线查询问题:每次查询单独给出,必须立即处理并返回结果,无法利用后续查询的信息。

离线查询的核心优势

通过 “提前知晓所有查询” 的特性,可以对查询和图数据进行全局优化,常见策略包括:

  • 排序:将查询和图的边 / 节点按特定规则排序(如按权重、阈值等),使处理过程更高效(如本文代码中按k排序查询,按diff排序边)。
  • 批量处理:一次性处理多个查询,避免重复计算(如用并查集动态维护连通性,一次性添加符合条件的边)。
  • 复杂数据结构优化:结合线段树、树状数组等,通过预处理降低单查询的时间复杂度。

图论中常见的离线查询问题

  • 连通性查询:如本题目,判断 “当边的权重≤k 时,两个节点是否连通”。
  • 路径查询:如 “求多个查询中,两点间路径的最大边权最小值”。
  • 最短路径查询:在静态图中处理多个最短路径查询(如 Floyd 算法本质是离线处理所有点对查询)。
  • 计数问题:如 “统计多个查询中,满足特定条件的路径 / 节点数量”。

核心思路

采用离线处理策略:将所有查询和边按特定规则排序,然后用并查集(Union-Find) 动态添加符合条件的边,高效判断节点连通性。具体来说:

  • 边按 “高度差绝对值” 从小到大排序;
  • 查询按 “阈值 k” 从小到大排序;
  • 按 k 从小到大处理查询,每次先添加所有高度差≤当前 k 的边,再判断两个节点是否连通。

代码实现

1、定义节点和边的结构体
struct Edge {int u, v, diff;  // u、v:边的两个端点;diff:两点高度差的绝对值Edge(int u_, int v_, int diff_) : u(u_), v(v_), diff(diff_) {}bool operator<(const Edge& other) const {  // 进行小于号的运算符重载return diff < other.diff;  // 按diff升序排序}
};struct Query {int a, b, k, idx;  // a、b:查询的两个节点;k:阈值;idx:查询原始索引(用于结果排序)Query(int a_, int b_, int k_, int idx_) : a(a_), b(b_), k(k_), idx(idx_) {}bool operator<(const Query& other) const {  // 进行小于号的运算符重载return k < other.k;  // 按k升序排序}
};
  • Edge:存储边的信息,重载<用于按高度差排序。
  • Query:存储查询的信息,重载<用于按阈值 k 排序,idx用于最终结果按原始查询顺序输出。
2、并查集(Union-Find)实现
vector<int> parent;  // 存储每个节点的父节点int find(int x) {  // 查找x的根节点(带路径压缩优化)if (parent[x] != x) { // 如果x的父节点不是自己,那么说明x不是根节点,就继续往上查找parent[x] = find(parent[x]);  // 路径压缩:直接指向根节点}return parent[x];
}
void unite(int x, int y) {  // 合并x和y所在的集合x = find(x);  // 找到x的根节点y = find(y);  // 找到y的根节点if (x != y) {  // 若不在同一集合,合并(将y的根指向x的根)parent[y] = x;}
}

并查集是一种高效处理 “动态连通性” 问题的数据结构,支持两种核心操作:

  • find(x):查找 x 所在集合的根节点(路径压缩优化使查询接近 O (1))。
  • unite(x, y):将 x 和 y 所在的集合合并。
3、main函数逻辑
int main() {ios::sync_with_stdio(false);  // 关闭输入输出同步,加速cin/coutcin.tie(nullptr);int n, m, q;cin >> n >> m >> q;  // n:节点数;m:边数;q:查询数// 读取每个节点的高度(1-based索引,节点编号1~n)vector<int> h(n + 1);for (int i = 1; i <= n; ++i) {cin >> h[i];}// 读取所有边,计算高度差绝对值,存入edgesvector<Edge> edges;for (int i = 0; i < m; ++i) {int u, v;cin >> u >> v;int diff = abs(h[u] - h[v]);  // 边的权重:高度差绝对值edges.emplace_back(u, v, diff);}// 读取所有查询,存入queries(记录原始索引idx)vector<Query> queries;for (int i = 0; i < q; ++i) {int a, b, k;cin >> a >> b >> k;queries.emplace_back(a, b, k, i);  // idx=i表示第i个查询}// 关键:边按diff升序排序,查询按k升序排序sort(edges.begin(), edges.end());sort(queries.begin(), queries.end());// 初始化并查集:每个节点的父节点是自己parent.resize(n + 1);for (int i = 1; i <= n; ++i) {parent[i] = i;}vector<string> res(q);  // 存储查询结果(按原始索引顺序)int edge_idx = 0;  // 记录已添加到并查集的边的索引// 按k从小到大处理每个查询for (const auto& query : queries) {int a = query.a, b = query.b, k = query.k, idx = query.idx;// 第一步:添加所有diff ≤ 当前查询k的边(因为边已按diff排序)while (edge_idx < m && edges[edge_idx].diff <= k) {unite(edges[edge_idx].u, edges[edge_idx].v);  // 合并边的两个端点edge_idx++;  // 移动到下一条边}// 第二步:判断a和b是否连通(根节点是否相同)if (find(a) == find(b)) {res[idx] = "YES";  // 连通} else {res[idx] = "NO";   // 不连通}}// 按原始查询顺序输出结果for (const string& ans : res) {cout << ans << '\n';}return 0;
}

核心逻辑梳理

  • 输入读取:节点高度、边信息(计算高度差)、查询信息(记录原始索引)。
  • 排序:边按高度差升序,查询按 k 升序(确保处理查询时,边是从小到大依次添加的)。

离线处理:

  • 用edge_idx跟踪已添加的边,对于每个查询(按 k 从小到大),先添加所有高度差≤k 的边。
  • 边添加完成后,用并查集判断 a 和 b 是否连通,结果存入res的对应索引(保证原始顺序)。
  • 输出结果:按原始查询顺序输出所有结果。

时间复杂度

  • 排序边:O (m log m)
  • 排序查询:O (q log q)
  • 处理查询和添加边:每个边被添加一次,每个查询的find操作接近 O (1)(并查集路径压缩优化),总复杂度 O (m + q α(n))(α 是阿克曼函数,增长极慢,可视为常数)。
整体复杂度为 O (m log m + q log q),适合处理大规模数据。

关于这道题目中,对边按照差值diff从小到大排序,对查询按照k值从小到大排序的重要性

在这个问题中,边按差值(diff)排序、查询按k排序是核心优化手段,目的是配合并查集的特性实现高效处理。如果不排序,要么逻辑上无法正确执行,要么时间复杂度会大幅上升,失去算法的优势。

为什么必须排序?

我们先回顾核心逻辑:对于每个查询(a, b, k),需要判断 “只保留高度差≤k的边时,a和b是否连通”。
要高效解决这个问题,关键依赖并查集的 “增量合并” 特性:并查集擅长 “合并集合”,但不擅长 “拆分集合”(一旦两个节点被合并,很难再分开)。

边按diff排序 + 查询按k排序的必要性
  • 查询按k从小到大排序:
    当我们按k从小到大处理查询时,每次处理的k是递增的。这意味着:之前加入并查集的边(diff ≤ 前一个k),一定满足当前k’ ≥ 前一个k,因此这些边仍然有效,不需要移除。
    例如:处理k=2的查询时,之前为k=1加入的边(diff ≤1)仍然满足diff ≤2,可以继续保留在并查集中。
  • 边按diff从小到大排序:
    配合查询的排序,我们可以用一个指针(如代码中的edge_idx)依次扫描边。每次处理查询时,只需添加所有diff ≤ 当前k且尚未加入的边(因为边已按diff排序,指针只会向前移动,不会回头)。这样每个边只会被添加一次,总耗时O(m)。
不排序会导致什么问题?

如果不排序,有两种可能的处理方式,但都会存在严重问题:

  • 不排序查询,仍按原逻辑处理
    假设查询顺序是随机的(例如先处理k=5,再处理k=2):
    处理k=5时,会加入所有diff ≤5的边,合并大量集合。
    处理k=2时,需要 “只保留diff ≤2的边”,但并查集无法拆分之前合并的集合(例如diff=3的边在k=5时被加入,现在需要移除,但并查集做不到)。
    此时判断的连通性会包含diff>2的边的影响,结果完全错误。
  • 不排序边,按查询顺序处理
    即使查询按k排序,但边不排序:
    每次处理查询时,需要遍历所有边,检查是否diff ≤ 当前k且未加入过。
    例如:处理k=3时,需要遍历所有边,筛选出diff ≤3的边(无论之前是否处理过),这会导致重复检查,时间复杂度从O(m + q)变成O(qm)(q次查询,每次遍历m条边)。
    当m和q达到1e5时,O(q
    m)会达到1e10,完全无法运行。

总结

排序的核心目的是:

  • 利用查询k的递增性,避免并查集的 “拆分” 操作(并查集不支持拆分,只能增量合并);
  • 利用边diff的有序性,通过一次线性扫描完成所有边的添加,避免重复检查。

不排序的话,要么逻辑上无法正确维护连通性(因并查集无法拆分),要么时间复杂度爆炸,失去算法的实用价值。因此,排序是这个离线查询问题的 “灵魂优化”。

完整源码实现

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;struct Edge {int u, v, diff;Edge(int u_, int v_, int diff_) : u(u_), v(v_), diff(diff_) {}bool operator<(const Edge& other) const {return diff < other.diff;}
};struct Query {int a, b, k, idx;Query(int a_, int b_, int k_, int idx_) : a(a_), b(b_), k(k_), idx(idx_) {}bool operator<(const Query& other) const {return k < other.k;}
};vector<int> parent;int find(int x) {if (parent[x] != x) {parent[x] = find(parent[x]);}return parent[x];
}void unite(int x, int y) {x = find(x);y = find(y);if (x != y) {parent[y] = x;}
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int n, m, q;cin >> n >> m >> q;vector<int> h(n + 1);for (int i = 1; i <= n; ++i) {cin >> h[i];}vector<Edge> edges;for (int i = 0; i < m; ++i) {int u, v;cin >> u >> v;int diff = abs(h[u] - h[v]);edges.emplace_back(u, v, diff);}vector<Query> queries;for (int i = 0; i < q; ++i) {int a, b, k;cin >> a >> b >> k;queries.emplace_back(a, b, k, i);}sort(edges.begin(), edges.end());sort(queries.begin(), queries.end());parent.resize(n + 1);for (int i = 1; i <= n; ++i) {parent[i] = i;}vector<string> res(q);int edge_idx = 0;for (const auto& query : queries) {int a = query.a, b = query.b, k = query.k, idx = query.idx;while (edge_idx < m && edges[edge_idx].diff <= k) {unite(edges[edge_idx].u, edges[edge_idx].v);edge_idx++;}if (find(a) == find(b)) {res[idx] = "YES";} else {res[idx] = "NO";}}for (const string& ans : res) {cout << ans << '\n';}return 0;
}
http://www.dtcms.com/a/511460.html

相关文章:

  • 大型的PC网站适合vue做吗网页制作工具通常在什么上建立热点
  • C++字符串操作与递增递减运算符详解
  • Python 的基本数据类型与它们之间的关系
  • All in One Runtimes下载和安装图解(附安装包,适合新手)
  • Python多patch装饰器使用指南
  • Prometheus监控系统
  • 【Java-集合】Set接口
  • 安卓开发- Log日志工具类
  • 微信链接的微网站怎么做的wordpress注册邮件设置密码
  • 国学大师网站谁做的wordpress dante
  • asp.net网站开发 vs2017手机网站分页
  • 传统决策vs AI决策:效率之争的底层逻辑与正确选择
  • SecurityContext在分布式系统(如微服务)中如何传递?有哪些常见方案?
  • MinIO与HDFS对比测试
  • SAP SD销售订单创建接口分享
  • rabbitMQ 的安装和使用
  • 华为Java专业级科目一通过心得
  • [Android] AutoCMD+ v.1.3.5:安卓自动化工具
  • 从养殖场到实验室:小吉快检BL-08plus如何实现禽病检测效率提升300%?——真实案例深度解析
  • 衡阳手机网站建设外发加工费计入什么科目
  • 【JUnit实战3_06】第三章:JUnit 的体系结构(下)
  • 使用injected Provider在remix中调试合约的坑 -- 时间(或者最新块)更新不及时
  • 丽水市莲都建设分局网站湖南微网站开发
  • 笔试-最小组合数
  • Web UI自动化时,通过autoIT的解决window控件
  • 电商网站建设建议网站前端交互功能案例分析
  • Qt——窗口
  • [人工智能-大模型-20]:对比 Copilot 与国产替代方案(如通义灵码、百度Comate)
  • c语言和网站建设的关系平台网站开发可行性分析
  • gcc编译的过程及每个过程的作用