news 2026/4/28 18:53:33

从‘校门外的树’到线段树:用一道OJ题带你入门区间查询与修改

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从‘校门外的树’到线段树:用一道OJ题带你入门区间查询与修改

从‘校门外的树’到线段树:用一道OJ题带你入门区间查询与修改

当你第一次看到"校门外的树"这道题时,可能会觉得这不过是个简单的数组标记问题。确实,对于L=10000这样的小规模数据,暴力解法完全可行。但想象一下,如果L扩大到10^9呢?这时传统的数组解法就会因为内存不足而崩溃。这正是算法设计中常见的"规模扩展"问题,也是我们今天要探讨的核心。

1. 问题分析与基础解法

让我们先理解题目本质:在一条长度为L的马路上,初始每个整数点都有一棵树。给定M个区间,需要移除这些区间内的所有树,最后统计剩余树木数量。用编程术语来说,这就是一个典型的区间覆盖统计问题。

对于小规模数据(如L≤10000),最直观的解法是使用标记数组:

int trees[10001] = {0}; // 初始化所有位置为0表示有树 for(int i =0; i<m; i++){ scanf("%d%d", &a, &b); for(int j=a; j<=b; j++){ trees[j] = 1; // 标记为1表示树被移除 } } int remaining = 0; for(int i=0; i<=l; i++){ if(trees[i] == 0) remaining++; }

这种解法时间复杂度为O(M×L),当L=10000、M=100时,运算量在百万级别,现代计算机完全可以轻松处理。但问题在于:

  1. 当L=10^9时,需要约4GB内存(假设int为4字节),显然不现实
  2. 即使内存足够,O(M×L)的时间复杂度在M=10^5时将达到10^14次操作,远超合理范围

2. 离散化:处理大规模区间的第一把钥匙

面对大规模数据,我们需要更聪明的办法。离散化(Discretization)技术应运而生,它能将无限或极大的数据范围映射到有限的点上。

离散化的核心思想是:只关注区间的端点,忽略中间连续的部分。具体到本题:

  1. 收集所有区间的起点和终点
  2. 对这些点进行排序和去重
  3. 相邻两点之间的区域要么全部被移除,要么全部保留

实现步骤示例:

// 假设所有区间端点已存储在数组points中 sort(points.begin(), points.end()); points.erase(unique(points.begin(), points.end()), points.end()); int result = 0; for(int i=1; i<points.size(); i++){ if(!is_removed(points[i-1], points[i])){ // 判断该区间是否被任何原始区间覆盖 result += points[i] - points[i-1] -1; // 计算区间内树的数量 } }

离散化将复杂度从O(L)降到了O(M log M),因为主要开销在于排序。这使得处理L=10^9、M=10^5的数据成为可能。

提示:离散化后判断区间是否被覆盖时,可以使用前缀和或差分数组优化查询效率

3. 线段树:区间操作的终极武器

虽然离散化解决了内存问题,但对于需要频繁区间查询和修改的场景(如动态增减区间),离散化就显得力不从心了。这时,线段树(Segment Tree)闪亮登场。

线段树是一种二叉树结构,每个节点代表一个区间,能够高效处理区间查询和更新操作。对于本题,线段树可以实现:

  • 区间更新:标记某个区间的树被移除
  • 区间查询:统计未被移除的树的数量

基础线段树节点结构:

struct SegmentTreeNode { int left, right; // 节点代表的区间范围 int count; // 该区间内剩余的树的数量 bool lazy; // 懒惰标记,表示该区间是否被整体移除 SegmentTreeNode *leftChild, *rightChild; void pushDown() { if(lazy && left != right) { leftChild->count = 0; rightChild->count = 0; leftChild->lazy = true; rightChild->lazy = true; lazy = false; } } };

关键操作实现:

// 区间更新 void update(SegmentTreeNode* node, int l, int r) { if(node->right < l || node->left > r) return; if(l <= node->left && node->right <= r) { node->count = 0; node->lazy = true; return; } node->pushDown(); update(node->leftChild, l, r); update(node->rightChild, l, r); node->count = node->leftChild->count + node->rightChild->count; } // 构建线段树 SegmentTreeNode* build(int l, int r) { SegmentTreeNode* node = new SegmentTreeNode{l, r, r-l+1, false, nullptr, nullptr}; if(l != r) { int mid = (l + r) / 2; node->leftChild = build(l, mid); node->rightChild = build(mid+1, r); } return node; }

使用线段树解决问题:

SegmentTreeNode* root = build(0, L); for(auto &range : ranges) { update(root, range.start, range.end); } int remaining = root->count;

线段树的优势在于:

  • 构建时间复杂度:O(L)
  • 每次更新/查询时间复杂度:O(log L)
  • 特别适合动态区间操作场景

4. 方案对比与选择指南

方法时间复杂度空间复杂度适用场景限制条件
暴力数组法O(M×L)O(L)L较小(≤10^6)内存限制
离散化O(M log M)O(M)静态区间,只需最终结果动态操作困难
线段树O(M log L)O(L)需要动态更新和查询实现较复杂

选择建议:

  1. 对于一次性静态查询且L极大时,优先考虑离散化
  2. 需要支持动态操作时,必须使用线段树
  3. 数据规模很小时,简单数组法最直观易实现

5. 线段树的扩展应用

线段树的价值远不止于此,它还能解决更多复杂问题:

  1. 区间最值查询:每个节点存储区间最大值/最小值
  2. 区间和查询:支持区间增减和求和操作
  3. 二维线段树:处理平面区域问题
  4. 持久化线段树:记录历史版本,解决可持久化问题

以区间和为例,节点结构稍作修改:

struct SegmentTreeNode { int left, right; int sum; // 区间和 int add; // 懒惰标记,表示区间每个元素要增加的值 void pushDown() { if(add != 0) { leftChild->sum += add * (leftChild->right - leftChild->left +1); rightChild->sum += add * (rightChild->right - rightChild->left +1); leftChild->add += add; rightChild->add += add; add = 0; } } };

这种灵活性使得线段树成为算法竞赛和工程开发中的重要工具。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/28 18:50:35

3大核心优化策略:用Win11Debloat让Windows系统重获新生

3大核心优化策略&#xff1a;用Win11Debloat让Windows系统重获新生 【免费下载链接】Win11Debloat A simple, lightweight PowerShell script that allows you to remove pre-installed apps, disable telemetry, as well as perform various other changes to declutter and c…

作者头像 李华
网站建设 2026/4/28 18:50:27

004 坐标系与刚体运动基础

004 坐标系与刚体运动基础 从一次电机堵转说起 去年调试一台四轮差速底盘&#xff0c;电机编码器读数突然跳变&#xff0c;上位机显示机器人原地转圈&#xff0c;实际却纹丝不动。排查三天&#xff0c;最后发现是IMU坐标系定义和电机编码器坐标系差了90度——我定义X轴朝前&…

作者头像 李华
网站建设 2026/4/28 18:43:35

深入理解Terraform中的Set类型

在Terraform中,数据类型是构建基础设施即代码的基石。其中,set类型虽然看似简单,但却常常被误解或使用不当。今天,我们将通过实例深入探讨Terraform中的set类型,理解其特性和应用场景。 Set类型的特性 首先,我们需要明确set类型的两个关键特性: 无序性:set中的元素是…

作者头像 李华
网站建设 2026/4/28 18:43:34

如何免费打造高效整洁桌面:NoFences开源分区管理终极指南

如何免费打造高效整洁桌面&#xff1a;NoFences开源分区管理终极指南 【免费下载链接】NoFences &#x1f6a7; Open Source Stardock Fences alternative 项目地址: https://gitcode.com/gh_mirrors/no/NoFences 你是否厌倦了杂乱无章的Windows桌面&#xff1f;NoFence…

作者头像 李华