HDU1542-Atlantis(扫描线+离散化个人小结)

Atlantis

Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 15590 Accepted Submission(s): 6400

Problem Description
There are several ancient Greek texts that contain descriptions of the fabled island Atlantis. Some of these texts even include maps of parts of the island. But unfortunately, these maps describe different regions of Atlantis. Your friend Bill has to know the total area for which maps exist. You (unwisely) volunteered to write a program that calculates this quantity.

Input
The input file consists of several test cases. Each test case starts with a line containing a single integer n (1<=n<=100) of available maps. The n following lines describe one map each. Each of these lines contains four numbers x1;y1;x2;y2 (0<=x1< x2<=100000;0<=y1< y2<=100000), not necessarily integers. The values (x1; y1) and (x2;y2) are the coordinates of the top-left resp. bottom-right corner of the mapped area.

The input file is terminated by a line containing a single 0. Don’t process it.

Output
For each test case, your program should output one section. The first line of each section must be “Test case #k”, where k is the number of the test case (starting with 1). The second one must be “Total explored area: a”, where a is the total explored area (i.e. the area of the union of all rectangles in this test case), printed exact to two digits to the right of the decimal point.

Output a blank line after each test case.

Sample Input
2
10 10 20 20
15 15 25 25.5
0

Sample Output
Test case #1
Total explored area: 180.00

题目:HDU1542
题意:给定二维平面中矩形左下和右上的坐标求所有矩形面积并的和。
思路:扫描线(刚开始接触扫描线的直接戳,本篇主要详细讲一下离散化,以及一些注意事项),坐标中有小数点,所以坐标需要离散化。
说一下什么是坐标离散化
对于这道题来说,我们知道线段树的区间下标是整数,这里出现了浮点数,显然直接用线段树去查询是不行的。
这里的离散化就是对原来每个坐标重新编号储存。当需要原始坐标的时候我们再将它拿出来。代码如下:

for(int i=1;i<cont;i++)if(posx[i]!=posx[i-1])posx[num++]=posx[i];/*离散坐标X*/

这里的cont是原始的边数,posx[]已经排序过了,我们只需要存去重后的坐标,num就是离散化后的边数。
除了处理浮点数坐标,当我们的坐标很大x<=1e9,以线段树这么吃内存的特性,直接存下标肯定会MLE的,这类问题的n一般不会很大,也就是说,x的个数不多,这时候离散化也是很有用的。
离散化后怎么使用下面讲。


假设我们已经离散好了坐标X,现在开始扫描了。

void pushup(int l,int r,int root)
{
    if(tree[root].vis)tree[root].ans=posx[r+1]-posx[l];
    else if(l==r)tree[root].ans=0;
    else tree[root].ans=tree[root*2].ans+tree[root*2+1].ans;
}
void update(int nl,int nr/*目标区间*/,int add,int l,int r,int root)
{
    if(nl==l&&nr==r)
    {
        tree[root].vis+=add;
        pushup(nl,nr,root);
        return ;
    }
    int mid=(l+r)/2;
    if(nr<=mid)update(nl,nr,add,l,mid,root*2);
    else if(nl>mid)update(nl,nr,add,mid+1,r,root*2+1);
    else update(nl,mid,add,l,mid,root*2),update(mid+1,nr,add,mid+1,r,root*2+1);
    pushup(l,r,root);
}
for(int i=0;i<cont-1/*扫描边界为实际边数-1,最后一边不需要扫*/;i++)/*扫描过程*/
{
    int l=lower_bound(posx,posx+num,edge[i].xl)-posx;/*利用low返回下标*/
    int r=lower_bound(posx,posx+num,edge[i].xr)-posx-1;
    update(l,r,edge[i].flag,0,num-1,1);
    ans+=tree[1].ans*(edge[i+1].h-edge[i].h);
}

这里我们先看第二框代码,我们现在从下往上扫描,只需要扫描cont-1条边,因为最后一边必定为上边,不需要扫描,更新线段树时,我们从原始边里得到两X坐标,去离散化后的posx[]里搜索,得到离散化后的坐标(posx[]的下标),搜素的时候,因为posx已经有序,所以用二分查找是效率最高的,方便写我们直接调用库函数lower_bound(首地址,尾地址,目标值)即可,它可以返还我们查找的目标值的地址,减去首地址就是我们想要的下标,查询r时,我们对坐标-1,这是另一个重要性质,区间左开右闭,等下再说。
更新函数update和线段树一样,没有什么变化,只需要注意,vis+=add,因为标记可以叠加。
在pushup函数中就和原来的线段树有些不同了。
一共3个if语句,我们分开看:
1.如果当前节点有标记,那么说明我们当前线段对答案是有贡献的,tree[root].ans=posx[r+1]-posx[l]。这里是等于,因为线段对于答案的贡献是覆盖,而不是叠加,如果一条大边覆盖了小边,那么对答案贡献的,只需要取大边的值即可。
2.如果当前节点没有标记,并且区间长度为0,tree[root].ans=0。这里说一下区间左开右闭的性质,假设我们有一颗线段树,管理区间[1,4].那么将其子节点展开:
这里写图片描述
如果我们要查[2,3],那么我们最后得到的应该是[2,2]和[3,3]两个子区间合并后的结果,但是(posx[2]-posx[2])+(posx[3]-posx[3])=0,显然不是我们想要的结果。怎么解决这个问题呢,很简单,我们让[2,2]不再表示一个点,而是一条线,即[2,3)=(posx[3]-posx[2]).所以我们查询[2,3]区间时,实际上是查询节点[2,2],这是为什么在扫描时,查到的r要减1,并且在pushup中第一个if里r要加1.在这里,既然[l,r],(l==r)这个区间有了实际的意义,但是它又没有子区间来更新它,所以我们手动更新它。
3.和线段树类似的根节点由子节点更新。
最后,我们完成了对树的更新,直接取根节点的答案,即是当前扫描线的长度。
对答案更新即可。

ans+=tree[1].ans*(edge[i+1].h-edge[i].h);

AC代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define met(s,k) memset(s,k,sizeof s)
#define scan(a) scanf("%d",&a)
using namespace std;
typedef long long ll;
const int maxn=1e6+10;
int cont/*原始边数*/,num/*离散化后的边数*/;
double posx[maxn]/*离散后的点*/;
struct Tree
{
    double ans;
    int vis;
}tree[maxn];
struct Edge
{
    double xl,xr,h;
    int flag;
    Edge(){};
    Edge(double a,double b,double c,int d) : xl(a),xr(b),h(c),flag(d){}
    bool operator< (const Edge &a)const{
        return h<a.h;
    }
}edge[maxn];
void pushup(int l,int r,int root)
{
    if(tree[root].vis)tree[root].ans=posx[r+1]-posx[l];
    else if(l==r)tree[root].ans=0;
    else tree[root].ans=tree[root*2].ans+tree[root*2+1].ans;
}
void update(int nl,int nr/*目标区间*/,int add,int l,int r,int root)
{
    if(nl==l&&nr==r)
    {
        tree[root].vis+=add;
        pushup(nl,nr,root);
        return ;
    }
    int mid=(l+r)/2;
    if(nr<=mid)update(nl,nr,add,l,mid,root*2);
    else if(nl>mid)update(nl,nr,add,mid+1,r,root*2+1);
    else update(nl,mid,add,l,mid,root*2),update(mid+1,nr,add,mid+1,r,root*2+1);
    pushup(l,r,root);
}
int main()
{
    int cas=0,n;
    while(scan(n),n)
    {
        double ans=0;
        met(tree,0);
        cont=0;
        num=1;/*初始化*/
        for(int i=0;i<n;i++)
        {
            double x,y,z,m;
            scanf("%lf%lf%lf%lf",&x,&y,&z,&m);
            edge[cont]=Edge(x,z,y,1);
            posx[cont++]=x;
            edge[cont]=Edge(x,z,m,-1);
            posx[cont++]=z;
        }
        sort(posx,posx+cont);
        sort(edge,edge+cont);
        for(int i=1;i<cont;i++)if(posx[i]!=posx[i-1])posx[num++]=posx[i];/*离散坐标X*/
        for(int i=0;i<cont-1/*扫描边界为实际边数-1,最后一边不需要扫*/;i++)/*扫描过程*/
        {
            int l=lower_bound(posx,posx+num,edge[i].xl)-posx;/*利用low返回下标*/
            int r=lower_bound(posx,posx+num,edge[i].xr)-posx-1;
            update(l,r,edge[i].flag,0,num-1,1);
            ans+=tree[1].ans*(edge[i+1].h-edge[i].h);
        }
        printf("Test case #%d\nTotal explored area: %0.2f\n",++cas,ans);
        printf("\n");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值