给定平面上若干矩形,求出被这些矩形覆盖过至少两次的区域的面积.



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 - 覆盖的面积(线段树-线段扫描 + 离散化(板题))的更多相关文章

  1. POJ-2528 Mayor's posters (线段树区间更新+离散化)

    题目分析:线段树区间更新+离散化 代码如下: # include<iostream> # include<cstdio> # include<queue> # in ...

  2. 权值线段树&线段树合并

    权值线段树 所谓权值线段树,就是一种维护值而非下标的线段树,我个人倾向于称呼它为值域线段树. 举个栗子:对于一个给定的数组,普通线段树可以维护某个子数组中数的和,而权值线段树可以维护某个区间内数组元素 ...

  3. 覆盖的面积(HDU 1255 线段树)

    覆盖的面积 Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Problem D ...

  4. 【BZOJ-3589】动态树 树链剖分 + 线段树 + 线段覆盖(特殊的技巧)

    3589: 动态树 Time Limit: 30 Sec  Memory Limit: 1024 MBSubmit: 405  Solved: 137[Submit][Status][Discuss] ...

  5. 【LeetCode】线段树 segment-tree(共9题)+ 树状数组 binary-indexed-tree(共5题)

    第一部分---线段树:https://leetcode.com/tag/segment-tree/ [218]The Skyline Problem [307]Range Sum Query - Mu ...

  6. 线段树(单标记+离散化+扫描线+双标记)+zkw线段树+权值线段树+主席树及一些例题

    “队列进出图上的方向 线段树区间修改求出总量 可持久留下的迹象 我们 俯身欣赏” ----<膜你抄>     线段树很早就会写了,但一直没有总结,所以偶尔重写又会懵逼,所以还是要总结一下. ...

  7. POJ2528:Mayor's posters(线段树区间更新+离散化)

    Description The citizens of Bytetown, AB, could not stand that the candidates in the mayoral electio ...

  8. hihoCoder #1079 : 离散化 (线段树,数据离散化)

    题意:有一块宣传栏,高一定,给出长度,再给出多张海报的张贴位置,问还能见到几张海报(哪怕有一点被看到)?假设海报的高于宣传栏同高. 思路:问题转成“给出x轴上长为L的一条线段,再用n条线段进行覆盖上去 ...

  9. POJ 2528 Mayor’s posters (线段树段替换 && 离散化)

    题意 : 在墙上贴海报, n(n<=10000)个人依次贴海报,给出每张海报所贴的范围li,ri(1<=li<=ri<=10000000).求出最后还能看见多少张海报. 分析 ...

随机推荐

  1. 讨论一下.NET里,对cookie身份验证的超时的处理

    引言 在.NET里提供了FormsAuthentication类用来对用户身份进行验证和授权.不过,对于cookie的超时处理,一直是一个头疼的问题.这里介绍一下微软对.NET 身份验证超时的处理机制 ...

  2. Win10系统下安装tensorflow(cpu)+keras+jupyter notebook运行环境

    记录,自用 1.安装Anaconda(这里安装的是python3.6版本) 2.创建tensorflow的conda环境 conda create -n tensorflow python=3.6 3 ...

  3. 分布式图数据库 Nebula Graph 的 Index 实践

    导读 索引是数据库系统中不可或缺的一个功能,数据库索引好比是书的目录,能加快数据库的查询速度,其实质是数据库管理系统中一个排序的数据结构.不同的数据库系统有不同的排序结构,目前常见的索引实现类型如 B ...

  4. 蓝牙技术 A2DP AVRCP BlueZ

    BlueZ 做为 linux 标准的协议栈,提供非常多的 profile ,各种的支持,ble , 蓝牙网络,文件传输,a2dp 音频传输. A2DP——Advanced Audio Distribu ...

  5. 编译 ijg JPEG V8 库 GIF 库

    libjpeg-turbo-1.2.1太老了,不支持,从内存解压,这里编译支持 jpeg_mem_src 的 JPEG V9 wget http://www.ijg.org/files/jpegsrc ...

  6. Java集合01——List 的几个实现类,了解一下?

    从本文起,我们将开始分享 Java 集合方面的知识,关注公众号「Java面典」了解更多 Java 知识点. List 是继承于 Collection 的接口,其实现类有 ArrayList,Linke ...

  7. Simulink仿真入门到精通(十三) Simulink创建自定义库

    当用户自定义了一系列模块之后,可以自定义模块库将同类自定义模块显示到Simulink Browser中,作为库模块方便地拖曳到新建模型中. 建立这样的自定义库需要3个条件: 建立library的mdl ...

  8. Simulink仿真入门到精通(十二) Publish发布M文件

    12.1 M文件的注释 使用%进行注释. 连续多行注释Ctrl+R,取消注释Ctrl+T. 12.2 Cell模式 在MATLAB脚本文件中使用连续两个注释符,开启一个新的Cell块,%%后空一格追加 ...

  9. 一键配置openstack-cata版的在线yum源

    下面脚本可以直接复制来配置openstack-ocata版的yum源: echo "nameserver 8.8.8.8 nameserver 119.29.29.29 nameserver ...

  10. vue项目中使用Lodop实现批量打印html页面和pdf文件

    1.Lodop是什么? Lodop(标音:劳道谱,俗称:露肚皮)是专业WEB控件,用它既可裁剪输出页面内容,又可用程序代码直接实现复杂打印.控件功能强大,却简单易用,所有调用如同JavaScript扩 ...