H - 覆盖的面积(线段树-线段扫描 + 离散化(板题))
给定平面上若干矩形,求出被这些矩形覆盖过至少两次的区域的面积.
Input
输入数据的第一行是一个正整数T(1<=T<=100),代表测试数据的数量.每个测试数据的第一行是一个正整数N(1<=N<=1000),代表矩形的数量,然后是N行数据,每一行包含四个浮点数,代表平面上的一个矩形的左上角坐标和右下角坐标,矩形的上下边和X轴平行,左右边和Y轴平行.坐标的范围从0到100000.
注意:本题的输入数据较多,推荐使用scanf读入数据.
Output
对于每组测试数据,请计算出被这些矩形覆盖过至少两次的区域的面积.结果保留两位小数.
知识点简介
- 线段树扫描可以横向扫描水平的边,也可纵向扫描竖直的边。
- 这里我们们只讨论 扫描水平边的情况,我们先引入两个概念:
上行边、下行边
,上行边就是某个矩形上边的那条边,下行边就是某个举行下边的那条边。 - 线段树扫描线段树的本质:就是将所有的给的矩形,把它们的水平线一一的抽出来,按水平线的高度从下到上一一添加边,在添加水平边的时候,我们要不断维护扫描线在x轴的有效长度,我们每添加一条下行边,那么这个我们就要在这个扫描线所在x轴上的区间的所有的sum[ ](
这里的的sum其实是区间和
)加1(如下图扫描线1:在(10,20)的区间的值都加1 ->sum[10,20]),如过每增加一条上行边(如:扫描线3),那么所有的相应的区间sum都减1,而这样通过这样不断的添加扫描线通过sum[1]来获取总的当前总的扫描线在x轴上的有效扫描面积,而两条扫描之间的高度差是很容易得到的,这样通 两条扫描的高度差 x 扫描线在x轴上的有效距离 就可求出两条扫描线之间的有效距离了。通过不断的添加扫描线并重复上述过程就可得到总的有效面积了 - 线段树的有一个千万要注意的地方: 线段树的叶节点存储的是什么?? 如果不考虑离散化的话:叶节点存储的是 矩形的端点横坐标(排序后的),这样通过两个叶节点相减(横坐标相减),我们就能得到一个长度区间,而这个区间有可能是扫描线在x轴有效长度的一个组成部分,这样同多个相应的叶节点横坐标相减就能够得到 总的扫描的线的有效长度
- 还有一个要注意的地方:就是当我代入右边界r到 Update() 函数的时候我们带的是 r - 1, 而在Push_up()函数中用到的又是
(r - 1) + 1
,这个地方希望我们都仔细想想。。理解理解
dalao线段树讲解传送门
接下来继续贴几张别人的图来帮助自己理解:
代码一(扫描线)
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define rtr rt << 1 | 1
#define rtl rt << 1
const int maxn = 10005;
struct Line
{
double l, r, h;
int d;
bool operator < (const Line & a) const
{
return h < a.h;
}
} line[maxn];
int mrk[maxn << 2];
double sum1[maxn << 2];
double sum2[maxn << 2];
double X[maxn];
void init()
{
memset(mrk, 0, sizeof(mrk));
memset(sum1,0, sizeof(sum1));
memset(sum2,0, sizeof(sum2));
}
void Push_up(int rt, int l, int r)
{
if(mrk[rt]) sum1[rt] = X[r + 1] - X[l];
else if(l == r) sum1[rt] = 0;
else sum1[rt] = sum1[rtl] + sum1[rtr];
if(mrk[rt] >= 2) sum2[rt] = X[r + 1] - X[l];
else if(l == r) sum2[rt] = 0;
else if(mrk[rt] == 1) sum2[rt] = sum1[rtl] + sum1[rtr];
else sum2[rt] = sum2[rtl] + sum2[rtr];
}
void Update(int rt, int l, int r, int s, int e, int d)
{
if(s <= l && r <= e)
{
mrk[rt] += d;
Push_up(rt ,l ,r);
return;
}
int m = l + r >> 1;
if(s <= m) Update(rtl, l, m, s, e, d);
if(e > m) Update(rtr, m+1, r, s, e, d);
Push_up(rt, l, r);
}
int Sch(double val, double X[], int len)
{
int l = 0, r = len - 1;
while(l <= r)
{
int m = (l + r) >> 1;
if(X[m] == val) return m;
if(X[m] > val) r = m - 1;
else l = m + 1;
}
return -1;
}
int main()
{
//freopen("A.txt","r",stdin);
int t;
scanf("%d", &t);
while(t --)
{
init();
int n;
scanf("%d", &n);
double x1, y1, x2, y2;
int a = 0;
for(int i = 1; i <= n; i ++)
{
scanf("%lf %lf %lf %lf", &x1, &y1, &x2, &y2);
X[a] = x1;
line[a ++] = (Line){ x1, x2, y1, 1};
X[a] = x2;
line[a ++] = (Line){ x1, x2, y2, -1};
}
//排序
sort(X, X + a);
sort(line, line + a);
//离散化
int b = 1;
for(int i = 1; i < a; i ++)
if(X[i] != X[i - 1])
X[b ++] = X[i];
//遍历讨论
double ans = 0;
for(int i = 0; i < a - 1; i ++)
{
int s = Sch(line[i].l, X, b);
int e = Sch(line[i].r, X, b) - 1;
Update(1, 0, b-1, s, e, line[i].d);
ans += sum2[1] * (line[i+1].h - line[i].h);
}
printf("%.2lf\n", ans);
}
return 0;
}
代码二(暴力递归下方标记)
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define db double
#define rtl rt << 1
#define rtr rt << 1 | 1
const int mxn = 10005;
struct Line
{
db l, r, h;
int d;
bool operator < (const Line &a) const
{
return h < a.h;
}
} line[mxn];
struct Node
{
int cnt, lazy;
db sum;
} tre[mxn << 2];
db X[mxn];
void Push_up(int rt, int l, int r)
{
if(tre[rt].cnt >= 2) tre[rt].sum = X[r + 1] - X[l];
else if(l == r) tre[rt].sum = 0;
else tre[rt].sum = tre[rtl].sum + tre[rtr].sum;
}
void Push_down(int rt, int l, int r)
{
if(! tre[rt].lazy) return;
tre[rtl].cnt += tre[rt].lazy;
tre[rtr].cnt += tre[rt].lazy;
tre[rtl].lazy += tre[rt].lazy;
tre[rtr].lazy += tre[rt].lazy;
tre[rt].lazy = 0;
}
void Build(int rt, int l, int r)
{
tre[rt].cnt = tre[rt].lazy = tre[rt].sum = 0;
if(l == r) return;
int m = (l + r) >> 1;
Build(rtl, l, m);
Build(rtr, m+1, r);
}
void Update(int rt, int l, int r, int s, int e, int d)
{
if(s <= l && r <= e)
{
tre[rt].cnt += d;
tre[rt].lazy += d; //这个区间更新了,打一个标记
Push_up(rt, l , r); //更行这个区间是因为,父区间可能用得到
return;
}
Push_down(rt, l, r);
int m = (l + r) >> 1;
if(s <= m) Update(rtl, l, m, s, e, d);
if(e > m) Update(rtr, m+1, r, s, e, d);
Push_up(rt, l, r);
}
void Query(int rt, int l, int r, int s, int e)
{
if(l == r)
{
Push_up(rt, l, r);
return;
}
Push_down(rt, l, r); //查询的时候将所有经过的层下放
int m = (l + r) >> 1;
if(s <= m)
Query(rtl, l, m, s, e);
if(e > m)
Query(rtr, m+1, r, s, e);
Push_up(rt, l, r);
}
int Sch(db val, db X[], int len)
{
int l = 0, r = len - 1;
while(l <= r)
{
int m = (l + r) >> 1;
if(X[m] == val) return m;
if(X[m] > val) r = m - 1;
else l = m + 1;
}
return -1;
}
int main()
{
//freopen("A.txt","r",stdin);
int t;
scanf("%d", &t);
while(t --)
{
int n;
scanf("%d", &n);
db x1, y1, x2, y2;
int a = 0;
for(int i = 1; i <= n; i ++)
{
scanf("%lf %lf %lf %lf", &x1, &y1, &x2, &y2);
X[a] = x1;
line[a ++] = (Line){ x1, x2, y1, 1 };
X[a] = x2;
line[a ++] = (Line){ x1, x2, y2, -1 };
}
sort(X, X + a);
sort(line, line + a);
int b = 1;
for(int i = 1; i < a ; i ++)
if(X[i] != X[i - 1])
X[b ++] = X[i];
Build(1, 0, b - 1);
db ans = 0;
for(int i = 0; i < a - 1; i ++)
{
int s = Sch(line[i].l, X, b);
int e = Sch(line[i].r, X, b) - 1;
Update(1, 0, b-1, s, e, line[i].d);
Query(1, 0, b-1, s, e);
ans += tre[1].sum * (line[i + 1].h - line[i].h);
}
printf("%.2lf\n", ans);
}
return 0;
}
H - 覆盖的面积(线段树-线段扫描 + 离散化(板题))的更多相关文章
- POJ-2528 Mayor's posters (线段树区间更新+离散化)
题目分析:线段树区间更新+离散化 代码如下: # include<iostream> # include<cstdio> # include<queue> # in ...
- 权值线段树&线段树合并
权值线段树 所谓权值线段树,就是一种维护值而非下标的线段树,我个人倾向于称呼它为值域线段树. 举个栗子:对于一个给定的数组,普通线段树可以维护某个子数组中数的和,而权值线段树可以维护某个区间内数组元素 ...
- 覆盖的面积(HDU 1255 线段树)
覆盖的面积 Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Problem D ...
- 【BZOJ-3589】动态树 树链剖分 + 线段树 + 线段覆盖(特殊的技巧)
3589: 动态树 Time Limit: 30 Sec Memory Limit: 1024 MBSubmit: 405 Solved: 137[Submit][Status][Discuss] ...
- 【LeetCode】线段树 segment-tree(共9题)+ 树状数组 binary-indexed-tree(共5题)
第一部分---线段树:https://leetcode.com/tag/segment-tree/ [218]The Skyline Problem [307]Range Sum Query - Mu ...
- 线段树(单标记+离散化+扫描线+双标记)+zkw线段树+权值线段树+主席树及一些例题
“队列进出图上的方向 线段树区间修改求出总量 可持久留下的迹象 我们 俯身欣赏” ----<膜你抄> 线段树很早就会写了,但一直没有总结,所以偶尔重写又会懵逼,所以还是要总结一下. ...
- POJ2528:Mayor's posters(线段树区间更新+离散化)
Description The citizens of Bytetown, AB, could not stand that the candidates in the mayoral electio ...
- hihoCoder #1079 : 离散化 (线段树,数据离散化)
题意:有一块宣传栏,高一定,给出长度,再给出多张海报的张贴位置,问还能见到几张海报(哪怕有一点被看到)?假设海报的高于宣传栏同高. 思路:问题转成“给出x轴上长为L的一条线段,再用n条线段进行覆盖上去 ...
- POJ 2528 Mayor’s posters (线段树段替换 && 离散化)
题意 : 在墙上贴海报, n(n<=10000)个人依次贴海报,给出每张海报所贴的范围li,ri(1<=li<=ri<=10000000).求出最后还能看见多少张海报. 分析 ...
随机推荐
- OOM when allocating tensor of shape [] and type float [[node conv2d_224/kernel/Initializer/random_uniform/min (defined at ./intances/utils.py:19) ]]
当你们在用模型加载,tensorflow-gpu预测的时候,有没有出现这样的错误?? 经过网上的资料查阅,更多的解释是GPU的显存不足造成的,于是乎,我查看了一下GPU显存:nvidia-smi 不看 ...
- 对Ajax的原理的理解和使用
1.什么是ajax? AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML). AJAX 不是新的编程语言,而是一种使用现有标准的新方 ...
- forEach 循环数组 # for in 循环对象 key # for of 循环对象 value
forEach 循环数组 # for in 循环对象 key # for of 循环对象 value
- debug.js在手机上打印调试信息
在做移动端开发的时候大家应该都遇到过这么一个问题:如何在手机上打印调试信息? 在pc端我们通常会用console.log 或者 alert,但大家知道console.log在手机上是看不到打印信息的: ...
- 通过shell脚本排查jar包中类冲突
当我们在线上运行项目时,依赖很多jar包,有时候某个类的全限定名,在多个包中出现,而某个包中的类的方法没有,而且在类加载时,刚好加载了这个类,可能会报找不到方法,或者找不到类的异常,这种情况就可能是类 ...
- Java并发编程之验证volatile不能保证原子性
Java并发编程之验证volatile不能保证原子性 通过系列文章的学习,凯哥已经介绍了volatile的三大特性.1:保证可见性 2:不保证原子性 3:保证顺序.那么怎么来验证可见性呢?本文凯哥(凯 ...
- 读书笔记——莫提默·J.艾德勒&查尔斯·范多伦(美)《如何阅读一本书》
第一篇 阅读的层次 第一章 阅读的活力与艺术 阅读的目标:娱乐.获得资讯.增进理解力这本书是为那些想把读书的主要目的当作是增进理解能力的人而写.何谓阅读艺术?这是一个凭借着头脑运作,除了玩味读物中的一 ...
- Core3.1WebApi_ 同源策略_如何支持跨域(转载)
原文:https://mp.weixin.qq.com/s/id3fOyGrZI9lLx7PKbVYlg
- M-Renamer方法名修改器,iOS项目方法名重构,Objective-C/Swift,代码模型预判,减少误改的机率,替换速度更快,可视化操作,傻瓜式操作,一键操作,引用处自动修改,马甲包的福音
M-Renamer M-Renamer(Method-Name-Renamer)类方法名修改器,采用链式解析头文件,代码模型预判,减少误改的机率,替换速度更快:可以解析整个项目大多数类的方法,可视化操 ...
- 浅谈Java语言环境搭建-JDK8
title: 浅谈Java语言环境搭建-JDK8 blog: CSDN data: Java学习路线及视频 1.What's the JDK,JRE JDK(Java Development Kit ...