求矩形并的面积(线段树)【模板】

本文介绍了一种使用线段树解决矩形面积并计算的方法。通过离散化处理和二分查找实现高效的区间覆盖更新与测度计算。适用于解决如POJ 1151等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=210;
struct node{
    int left,right,c; //c : 区间被覆盖的层数, m: 区间的测度 
    double m;
}tree[N*4];
struct Line{
    double x,y1,y2;  //纵方向直线, x:直线横坐标, y1 y2:直线上的下面与上面的两个纵坐标
    int s;   //s = 1 : 直线为矩形的左边, s = 0:直线为矩形的右边
}line[N];
bool cmp(Line a,Line b){
    return a.x<b.x;
}
double ty[N],y[N];   //y[] 整数与浮点数的对应数组; ty[]:用来求y[]的辅助数组
void build(int t,int left,int right){
    tree[t].left=left;
    tree[t].right=right;
    tree[t].c=0;
    tree[t].m=0;
    if(left+1<right){
        int mid=(left+right)>>1;
        build(t<<1,left,mid);
        build((t<<1)+1,mid,right);
    }
}
void update(int t){
    if(tree[t].c>0) tree[t].m=y[tree[t].right]-y[tree[t].left];
    //将线段树上区间的端点分别映射到 y[]数组所对应的浮点数上,由此计算出测度 
    else if(tree[t].left+1==tree[t].right) tree[t].m=0; 
    else tree[t].m=tree[t<<1].m+tree[(t<<1)+1].m;
}
void insert(int t,int left,int right){
    if(left<=tree[t].left && tree[t].right<=right){
        tree[t].c++;
        update(t);
        return ;
    }
    int mid=(tree[t].left+tree[t].right)>>1;
    if(left<mid) insert(t<<1,left,right);
    if(right>mid) insert((t<<1)+1,left,right);
    update(t);
}
void del(int t,int left,int right){
    if(left<=tree[t].left && tree[t].right<=right){
        tree[t].c--;
        update(t);
        return ;
    }
    int mid=(tree[t].left+tree[t].right)>>1;
    if(left<mid) del(t<<1,left,right);
    if(right>mid) del((t<<1)+1,left,right);
    update(t);
}

int getindex(int n,double x){//二分查找出浮点数 t 在数组y[]中的位置(此即所谓的映射关系)
    int left,right,mid;
    left=1;right=n;
    while(left<=right){
        mid=(left+right)>>1;
        if(y[mid]<x) left=mid+1;
        else right=mid-1;
    }
    return left;
}
int main(){
    int n,i;
    double x1,y1,x2,y2;
    int cas=1;
    while(scanf("%d",&n)!=EOF){
        if(n==0) break;
        for(i=0;i<n;i++){
            scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
            line[i*2].x=x1; line[i*2].y1=y1; line[i*2].y2=y2; line[i*2].s=1;
            line[i*2+1].x=x2; line[i*2+1].y1=y1; line[i*2+1].y2=y2; line[i*2+1].s=0;
            ty[i*2]=y1;ty[i*2+1]=y2;
        }
        n<<=1;
        sort(line,line+n,cmp);
        sort(ty,ty+n);  //默认升序排序
        int num=1;
        y[1]=ty[0];
        for(i=1;i<n;i++){ //离散化处理数组 ty[]使之不含重覆元素,得到新的数组存放到数组y[]中 
            if(ty[i]!=ty[i-1]) y[++num]=ty[i];
        }
        build(1,1,num);//树的叶子节点与数组 y[]中的元素个数相同,以便建立一一对应的关系
        int left,right;
        double ans=0;
        for(i=0;i<n-1;i++){
            left=getindex(num,line[i].y1);  //由对应关系计算出线段两端在树中的位置
            right=getindex(num,line[i].y2);
            if(line[i].s==1)   //插入矩形的左边
                insert(1,left,right);
            else         //删除矩形的右边 
                del(1,left,right);
            ans+=tree[1].m*(line[i+1].x-line[i].x);
        }
        printf("Test case #%d\n",cas++);
        printf("Total explored area: %.2lf\n\n",ans);
    }
    return 0;
}

代码转自:http://www.cnblogs.com/CXCXCXC/p/5003389.html(poj1151)

<think>好的,用户现在想找与线段树相关的例题或应用场景,特别是示例题目和练习题。我需要根据提供的引用和之前的回答来整理这些信息。 首先,回顾用户之前的提问,他们详细询问了线段树的基础概念、结构、操作以及应用场景。现在用户希望得到具体的例题来练习,这可能意味着他们想通过实践加深理解。引用中提到了几个相关的练习题,比如引用[2]中的K-th Number问题,引用[4]的线段树模板练习题,以及引用[5]中的牛客竞赛题目。这些可以作为例题的来源。 接下来,需要确定如何分类这些例题。通常,线段树的题目可以分为基础题和进阶题。基础题可能涉及单点更新、区间查询,比如区间和或最大值。进阶题可能涉及区间修改、延迟标记(Lazy Propagation),或者与其他算法结合的问题。 引用[2]中的K-th Number问题涉及建立线段树存储排序后的子数组,然后进行归,这可能需要用到归排序线段树来处理区间第K大数的问题。这是一个经典的应用场景,适合作为进阶例题。引用[5]中的题目要处理区间元素的累加操作,例如每个位置加平方数,然后查询区间和,这需要用到延迟标记来高效处理区间修改,属于进阶题。 另外,引用[3]提到LeetCode的基础题目,比如Range Sum Query和Range Minimum Query,这些都是典型的线段树应用。用户可能需要这些基础题来巩固线段树的构建、查询和更新操作。 应用场景方面,用户之前的回答已经提到动态区间统计、碰撞检测等,但需要更具体的例子,如股票分析、游戏开发中的实时查询,或者计算几何问题。例如,引用[1]中的股票价格波动分析可以作为一个应用实例。 需要注意用户提供的引用中有部分内容可能不直接相关,比如引用[4]的订单问题,可能需要结合线段树处理区间覆盖或判断订单满足情况,但题目描述不够详细,需要谨慎处理。另外,引用[5]的题目涉及复杂的区间操作,每个位置加不同的值,这需要设计特殊的延迟标记来处理,是一个好的进阶例题。 最后,要确保提供的例题有明确的描述和对应的解决思路,帮助用户理解如何应用线段树。同时,分门别类地整理基础与进阶题目,方便用户逐步练习。另外,应用场景需要具体化,让用户了解线段树的实际用途,增强学习的动力。</think>### 线段树例题与应用场景详解 --- #### 一、线段树基础例题 以下是线段树典型的基础练习题,涵盖单点更新、区间查询等核心操作: ##### 1. **区间和查询(Range Sum Query)** **题目描述** 给定数组$arr$,支持以下操作: - 单点更新:将$arr[i]$修改为$val$ - 区间查询:计算$arr[l..r]$的和 **解决思路** 直接套用线段树模板,节点存储区间和,合操作为加法。 **LeetCode例题**:[303. Range Sum Query - Immutable](https://leetcode.com/problems/range-sum-query-immutable/)(静态数组) **变种题目**:[307. Range Sum Query - Mutable](https://leetcode.com/problems/range-sum-query-mutable/)(动态数组) --- ##### 2. **区间最小值查询(Range Minimum Query)** **题目描述** 给定数组$arr$,支持以下操作: - 单点更新:修改$arr[i]$的值 - 区间查询:找出$arr[l..r]$的最小值 **解决思路** 线段树节点存储区间最小值,合操作为取最小值。 **例题**:[LeetCode 剑指 Offer 59 - II. 队列的最大值](https://leetcode.cn/problems/dui-lie-de-zui-da-zhi-lcof/)(类似思路) --- #### 二、进阶例题(含区间修改与延迟标记) 以下题目需结合**延迟传播(Lazy Propagation)**优化: ##### 1. **区间加法与区间和查询** **题目描述** 给定数组$arr$,支持操作: - 区间修改:将$arr[l..r]$每个元素增加$k$ - 区间查询:计算$arr[l..r]$的和 **解决思路** 在节点中维护区间和与延迟标记,更新时传递标记到子节点。 **LeetCode例题**:[370. Range Addition](https://leetcode.com/problems/range-addition/)(差分数组也可解) **模板代码**: ```python def update_lazy(self, node, l, r, ul, ur, val): if ul > r or ur < l: # 无交集 return if ul <= l and r <= ur: # 完全覆盖 self.tree[node] += (r - l + 1) * val self.lazy[node] += val return # 向下传递延迟标记 mid = (l + r) // 2 self.push_down(node, mid - l + 1, r - mid) self.update_lazy(2*node+1, l, mid, ul, ur, val) self.update_lazy(2*node+2, mid+1, r, ul, ur, val) self.tree[node] = self.tree[2*node+1] + self.tree[2*node+2] ``` --- ##### 2. **复杂区间操作(平方累加)** **题目描述**[^5] 输入区间$[l, r]$,对第$i$个元素增加$i^2$(如第一个元素加$1$,第二个加$4$,依此类推),支持查询区间和。 **解决思路** 需维护多个延迟标记(如$\sum i^2$的系数),推导合公式: - 设区间为$[a, b]$,和为$S = \sum_{i=a}^{b} (i^2 \cdot k_1 + i \cdot k_2 + k_3)$ - 通过数学展开合标记 --- #### 三、应用场景实例 线段树的典型应用场景如下: 1. **动态区间统计** - **股票分析**:实时计算某时间段内的最高价、最低价或交易量总和[^1] - **传感器数据**:监控温度/湿度等传感器数据的区间波动 2. **游戏开发** - **碰撞检测**:快速判断多个物体的包围盒是否重叠(线段树存储空间划分) - **技能范围判定**:实时计算玩家技能覆盖区域内的目标 3. **计算几何** - **矩形覆盖面积**:合多个矩形的投影区间,计算总面积 - **线段交点检测**:通过区间扫描线算法加速查询 --- #### 四、经典例题推荐 | 题目类型 | 例题链接 | 核心考点 | |--------------------|--------------------------------------------------------------------------|---------------------------| | 静态区间最值 | [LeetCode 239. Sliding Window Maximum](https://leetcode.com/problems/sliding-window-maximum/) | 线段树维护区间最大值 | | 区间染色问题 | [POJ 2528 Mayor's posters](http://poj.org/problem?id=2528) | 离散化+延迟标记覆盖操作 | | 区间第K大数 | [SPOJ KQUERY](https://www.spoj.com/problems/KQUERY/) | 归排序树(线段树变种) | | 动态区间众数 | [CodeChef FREQ](https://www.codechef.com/problems/FREQ) | 复杂统计量的维护与合 | --- #### 五、练习题参考答案思路 1. **如何处理区间修改操作?** - 使用**延迟标记(Lazy Propagation)**,将修改操作暂存于父节点,在查询或后续修改时传递到子节点,避免重复计算[^3]。 2. **线段树与树状数组的核心区别?** - **功能**:线段树支持任意区间操作(和、最值、自定义合),树状数组仅支持前缀操作(如前缀和) - **复杂度**:线段树单次操作$O(\log n)$,树状数组常数更小但功能受限[^4]。 3. **线段树空间复杂度为何是$O(4n)$?** - 完全二叉树的节点数上限为$2^{\lceil \log n \rceil + 1}$,实际取$4n$确保极端情况下不越界[^5]。 --- 通过上述例题和场景的练习,可以逐步掌握线段树的实现技巧与应用场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值