线段树区间扫描线超详解,一篇文章搞懂扫描线
怨念
这个专题其实不难,但是翻了一圈网上的博客,写得是云里雾里,我打算用一篇博客把它讲明白
前序知识
能看懂这篇文章需要:
- 线段树基础知识。
- 线段树染色问题基本概念。
- 离散化操作
目标
首先这个扫描线算法解决的是什么问题?
- 主要解决的是ACM中的
- 矩形面积问题
- 矩形周长问题
- 多边形面积问题
这篇博客主讲的问题是:
- 求矩形面积并 HDU 1542
题目分析
这道题最主要就是让我们求解矩形面积并,求解矩形面积并如果不用任何优化方式,那就是这么算的。
用矩形和减去矩形交集:
(20−10)∗(20−10)+(25−15)∗(25.5−15)−(20−15)∗(20−15)=180.0(20−10)∗(20−10)+(25−15)∗(25.5−15)−(20−15)∗(20−15)=180.0(20−10)∗(20−10)+(25−15)∗(25.5−15)−(20−15)∗(20−15)=180.0(20−10)∗(20−10)+(25−15)∗(25.5−15)−(20−15)∗(20−15)=180.0(20−10)∗(20−10)+(25−15)∗(25.5−15)−(20−15)∗(20−15)=180.0 (20-10)*(20-10)+(25-15)*(25.5-15)-(20-15)*(20-15) = 180.0(20−10)∗(20−10)+(25−15)∗(25.5−15)−(20−15)∗(20−15)=180.0(20−10)∗(20−10)+(25−15)∗(25.5−15)−(20−15)∗(20−15)=180.0(20−10)∗(20−10)+(25−15)∗(25.5−15)−(20−15)∗(20−15)=180.0(25.5−5)呢?
- 那是因为得用2号矩形的高那部分减去,减掉2号矩形下面多出来的部分。
那为什么有时候“多出来”是加上一个值,有时候“多出来”是减掉一个值呢?
这个问题其实也是得到高度最核心的问题,就是“入边”和“出边”的问题。
定义:在同一个矩形内,从左往右看,第一条看到的边为“入边”,第二条看到的边为“出边”
其实所谓的从左往右(也可以是从上往下),就是扫描线的方向。
当从左往右扫,遇到入边的线,则对入边区间扫到进行+1操作,遇到出边,那么对出边区间进行-1操作,这样子就可以解释“有时候“多出来”是加上一个值,有时候“多出来”是减掉一个值”这个问题了!
凭借刚才获得的知识,我们来思考步骤
- (出入边赋值已完成)
- 第一条为入边,区间为[10,20],则区间[10,20] +1(此时区间[10,20] = 1)
- 查看整个域的区间,只有[10,20]有值,则Kuan[0]*10 = 50
- 第二条边为入边,区间为[15,25.5],则[15,25.5]+1(此时区间[10,15]=1,[15,20]=2,[20,25.5]=1)
- 查看整个域区间,从[10,25.5]有值,则Kuan[1]*(25.5-10) = 77.5
- 第三条边为出边,区间从[10,20],则[10,20]-1(此时区间为[15,25.5] = 1)
- 查看整个区间,从[15,25.5]有值,则Kuan[2]*(25.5-15) = 52.5
- 第四条边为出边,区间从[15,25.5],此时-1,整个区间没掉
- 整个区间没值,遍历结束。
这下整个思路就非常清晰了
将上述的区间模型转化成线段树
问题1:
我这个下标可存不了25.5这种东西啊,而且它这个区间要是特别大,我的数组会存不下。
解决方案:
将区间离散化。
离散化(步骤)
- 把y坐标离散化
- 用一个区间数组记录每个区间的值:于是现在[10,15]成为块1,[15,20]成为块2,[20,25.5]成为块3。
- 则现在更新第一条入边[10,20]就变成更新[1,3],更新第二条边就变成更新[2,4],之后再查表全部乘起来即可
问题2:这个区间更新了之后,怎么维护区间信息,使得调用query()就可以返回总共存在值的区间长度?
这个和染色问题是一样的,用一个cover表示区间[left,right]被覆盖的次数,用len表示这个区间的合法长度,那query(1到n)的合法长度,自然就能返回 总共的区间长度了。
解题步骤:
建树:
int cover[maxn];//存放i节点对应覆盖情况的值
double length[maxn];//存放区间i下的总长度
double yy[maxn];//存放离散后的y值,下标用lowerbound进行查找
- 1
- 2
- 3
根据线段树的模板,结合这题的样例,我们很快就建好了一棵树。
仔细观察,这棵树似乎和之前的线段树不一样,它的叶子节点的[l,r]不相等,而是差别为1,。
这是因为点对于求面积的题目毫无意义,我们最需要的是它每一个基础的“块”。
- 第一条为入边,区间为[1,3],则区间cover[1,3] +1(此时区间[1,3] = 1)
- query整个域的区间,得到len=10,则Kuan[0]*10 = 50
- 第二条边为入边,区间为[2,4],则cover[2,4]+1(此时如图所示)
- query整个区间,得到len = 15.5,则Kuan[1]*(25.5-10) = 77.5
(注意:这里只需要上推len,不需要下推cover至[2,3]和[3,4],也不需要上推cover至[1,4]。这就是线段树的强大之处:只要找到对应结点的区间能完全覆盖当前线段区间就可以回溯统计了,并不需要更新到叶子节点,这是线段树为什么效率高的原因)
6. 第三条边为出边,区间从[1,3],则[1,3]-1
7.query整个区间,得到10.5,则Kuan[2]*10.5 = 52.5
7. 第四条边为出边,区间从[15,25.5],此时-1,整个区间没掉
8. query整个区间,值为0,遍历结束。
AC代码
#include<iostream>
#include<algorithm>
#include<string.h>
#define ls (rt<<1)
#define rs (rt<<1|1)
using namespace std;
const int maxn = 20005;
int cover[maxn];//存放i节点对应覆盖情况的值
double length[maxn];//存放区间i下的总长度
double yy[maxn];//存放离散后的y值,下标用lowerbound进行查找
struct ScanLine
{
double x;//边的x坐标
double upy,downy;//边的y坐标上,y坐标下
int inout;//入边为1,出边为-1
ScanLine(){}
ScanLine(double x,double y1,double y2,int io):x(x),upy(y1),downy(y2),inout(io){}
}line[maxn];
bool cmp(ScanLine &a,ScanLine &b)//x排序函数
{
return a.x<b.x;
}
void pushup(int l,int r,int rt)//pushup其实主要就思考在什么情况,需要更新哪些信息来维护线段树
{
if(cover[rt]) length[rt] = yy[r]-yy[l];//如果某个节点的cover为正,那么这个点的长度
else if(l+1r) length[rt] = 0;//到了叶子节点
else length[rt] = length[ls]+length[rs];
}
void update(int yl,int yr,int io,int l,int r,int rt)
{
if(yl>r||yr<l) return ;//极端情况?
if(yl<=l&&yr>=r)
{
cover[rt] += io;//根据出边入边,加上相应的值
pushup(l,r,rt);
return ;
}
if(l+1r)return ;//到子节点
int m = (l+r)>>1;
if(yl<=m)
update(yl,yr,io,l,m,ls);
if(yr>m)
update(yl,yr,io,m,r,rs);//这里不再是m+1,因为要进入类似[1,2][2,3]的叶子节点
pushup(l,r,rt);
}
int main()
{
//freopen(“hdu 1542.txt”,“r”,stdin);
int n,T = 0;//矩形个数,样例个数
while(scanf("%d",&n),n){
int cnt = 0;
double x1,x2,y1,y2;
int yr,yl;
int io;
for(int i = 1;i<=n;++i)
{
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);//输入数值
line[++cnt] = ScanLine(x1,y2,y1,1);//给入边赋值
yy[cnt] = y1;//获得y值
line[++cnt] = ScanLine(x2,y2,y1,-1);//给出边赋值
yy[cnt] = y2;//获得y的值
}
原文链接聚聚博客
#include <iostream>
#include <cstdio>
#include <stack>
#include <sstream>
#include <vector>
#include <map>
#include <cstring>
#include <deque>
#include <cmath>
#include <iomanip>
#include <queue>
#include <algorithm>
#include <set>
#define mid ((l + r) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define ms(a,al) memset(a,al,sizeof(a))
#define sfx(x) scanf("%lf",&x)
#define sfxy(x,y) scanf("%lf%lf",&x,&y)
#define sdx(x) scanf("%d",&x)
#define sdxy(x,y) scanf("%d%d",&x,&y)
#define pfx(x) printf("%.0f\n",x)
#define pfxy(x,y) printf("%.6f %.6f\n",x,y)
#define pdx(x) printf("%d\n",x)
#define pdxy(x,y) printf("%d %d\n",x,y)
#define _for(i,a,b) for( int i = (a); i < (b); ++i)
#define _rep(i,a,b) for( int i = (a); i <= (b); ++i)
#define for_(i,a,b) for( int i = (a); i >= (b); -- i)
#define rep_(i,a,b) for( int i = (a); i > (b); -- i)
#define lowbit(x) ((-x) & x)
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define INF 0x3f3f3f3f
#define hash Hash
#define next Next
#define f first
#define s second
using namespace std;
const int N = 1010, eps = 1e-10;
typedef long long LL;
typedef unsigned long long ULL;
int n;
struct ScanLine {double x;double upy, downy;int inout;bool operator < (const ScanLine & t) const {return x < t.x;}ScanLine(){}ScanLine(double x, double y1, double y2, int oi):x(x),upy(y1),downy(y2),inout(oi){}
}line[N << 3];
struct Node {int cover;double length, ss;
}tr[N << 3];
vector<double> ys;inline void pushup(int rt, int l, int r)
{if(tr[rt].cover) tr[rt].length = ys[r] - ys[l];else if(l + 1 == r) tr[rt].length = 0;else tr[rt].length = tr[rt << 1].length + tr[rt << 1|1].length;if(tr[rt].cover > 1) tr[rt].ss = ys[r] - ys[l];else if(l + 1 == r) tr[rt].ss = 0;else if(tr[rt].cover == 1) tr[rt].ss = tr[rt << 1].length + tr[rt << 1|1].length;else tr[rt].ss = tr[rt << 1].ss + tr[rt << 1|1].ss;
}inline void init()
{_for(i,0,N << 3)tr[i].cover = tr[i].length = tr[i].ss = 0;ys.clear();
}inline void update(int rt, int l, int r, int posl, int posr, int io)
{if(posl <= l && posr >= r){tr[rt].cover += io;pushup(rt,l,r);return;}if(l + 1 == r) return;if(posl <= mid) update(rt << 1,l,mid,posl,posr,io);if(posr > mid)update(rt << 1|1,mid,r,posl,posr,io);pushup(rt,l,r);return;
}inline int find(double x)
{return lower_bound(ys.begin(),ys.end(),x) - ys.begin();
}int main()
{int T;cin >> T;while(T --){scanf("%d",&n);int cnt = 0;ys.push_back(-1);_for(i,0,n){double x1, y1, x2, y2;scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);line[++ cnt] = ScanLine(x1, y2, y1, 1);line[++ cnt] = ScanLine(x2, y2, y1, -1);ys.push_back(y1), ys.push_back(y2);}sort(ys.begin(),ys.end());ys.erase(unique(ys.begin(),ys.end()),ys.end());int len = ys.size();sort(line + 1,line + cnt + 1);double ans = 0;_for(i,1,cnt+1){ans += (double) tr[1].ss * (line[i].x - line[i - 1].x);update(1,1,len,find(line[i].downy),find(line[i].upy),line[i].inout);} printf("%.2lf\n",ans);init();}return 0;
}
线段树区间扫描线超详解,一篇文章搞懂扫描线相关推荐
- 标准差详解-一文搞懂标准差的含义
标准差详解-一文搞懂标准差的含义 转载自 样本标准差的意义是什么? 的第一个回答
- 大疆云台和华为P30_超全,一篇文章搞清楚大疆Osmo三款产品区别!
超全,一篇文章搞清楚大疆Osmo三款产品区别! 2020-06-06 17:23:07 33点赞 179收藏 13评论 先说结论吧! Mobile 3适合日常用手机作为主力拍摄工具的人群,手机的拍摄能 ...
- c++ 计算正弦的近似值_一篇文章搞懂正弦保真性
本文介绍数字信号处理中"正弦保真性"这一概念,想要更好地理解本文所述内容,建议读者先阅读<一篇文章搞懂卷积>. 正弦保真性定义 一个正弦信号作为线性时不变系统的输入时, ...
- 一篇文章搞懂filebeat(ELK)
一篇文章搞懂filebeat(ELK) https://www.cnblogs.com/zsql/p/13137833.html 目录 一.filebeat是什么 1.1.filebeat和beats ...
- 线段树优化建图详解——区间连边之技巧,吊打紫题之利器
我们从一道例题开始. CF786B Description Solution 朴素解法: 暴力连边+最短路 对于每次连边操作,我们逐一连边,最后在图上跑一遍单源最短路径算法即可. 时间复杂度 O ( ...
- 超详细解释XGBoost,一篇文章搞懂XGBoost
XGBoost 文章目录 XGBoost 背景 工程原理 具体形式 怎么做出预测 目标函数 引言 数学详解 明确符号 化简目标函数 符号注释 结论 生成一棵完整的树 贪心算法 加权分位法 工作原理 数 ...
- 一篇文章搞懂算法基础
源码地址 https://github.com/javanan/DataStructure 目录 时间复杂度介绍 空间复杂度介绍 递归算法与非递归算法区别和转换 折半查找/二分查找算法 链表实现 反转 ...
- 一篇文章搞懂架构师的核心技能
" 这是架构师系列的第一篇:核心技能,希望这个系列能完全揭示架构师这个职位:我先从核心技能开始,后续还有架构师之路,架构实战等架构师系列文章. 本文作者 陈睿 优知学院创始人,前携程定制旅游 ...
- 【一篇文章搞懂】什么是分布式锁?为什么要用分布式锁?看这篇文章准没错!
简介 HikariCP 是用于创建和管理连接,利用"池"的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制.连接可靠性测试.连接泄露控制.缓存语句等功能,另外,和 dr ...
最新文章
- wireshark过虑规则
- Algs4-1.4.12找出两个有序数组的公共元素-方法1
- stateflow错误:Error in port widths or dimensions.
- WebClient UI忽略所有增强的开关
- dubbo yml配置_Spring boot 的profile功能如何实现多环境配置自动切换
- 请教设计模式大牛们几点
- C语言 extern - C语言零基础入门教程
- 超赞 | 计算机视觉联盟全新Logo!近期精华回顾!
- (50)常见命名方式
- 技本功丨用短平快的方式告诉你:Flink-SQL的扩展实现...
- python将一组数据转化为列表_Pandas将列表(List)转换为数据框(Dataframe)
- 使用electron开发指静脉客户端遇到的问题总结
- 刘晓攀:连滚带爬看完《你的知识需要管理》
- Mysql和Oracle索引简介
- Matlab 边界提取
- 盘点中国知名网络游戏公司
- 个人网站Timonj(Personal website)
- 「GoTeam 招聘时间」ANKER Golang 开发工程师(深圳)
- 记录:Flink checkpoint 过期导致失败(线上问题)
- r5 7530u和r7 5825u差距 r57530u和r75825u对比