首页 > 其他 > 详细

扫描法+线段树

时间:2019-07-23 13:59:36      阅读:82      评论:0      收藏:0      [点我收藏+]

题意

二维平面有n个平行于坐标轴的矩形,现在要求出这些矩形的总面积. 重叠部分只能算一次.

分析:

线段树的典型扫描线用法.

参考博客

https://blog.csdn.net/xianpingping/article/details/83032798

请思考后下翻(虽然遮不住

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAX 400+99

int n;
double sumv[MAX<<2];
int cnt[MAX<<2];
double hack[MAX];

//以横坐标作为线段(区间),对横着的线段进行扫描
//扫描的作用是每次更新下底边总长度和下底边个数,增加新面积
struct seg{
    double l, r, h;
    int d;  
    seg(){}
    seg(double l, double r, double h, int d) : l(l), r(r), h(h), d(d) {}
    bool operator < (const seg& xxx) const {
        return h < xxx.h ;
    }
}s[MAX];

void push_up(int o, int l, int r) {//更新sumv(这里的hack需要用到l和r 
    if(cnt[o]) sumv[o] = hack[r+1] - hack[l];//o的区间所表示的整个线段都可以作为底边 
    else if(l == r) sumv[o] = 0;//叶子结点表示[x,x),它的区间长度为0
    else sumv[o] = sumv[o<<1] + sumv[o<<1|1]; 
}

void optadd(int o, int l, int r, int ql, int qr, int d) {
    if(ql <= l && r <= qr) {
        cnt[o] += d;//更新 
        push_up(o, l, r);
        return ;
    }
    int mid = (l + r) >> 1;
    if(ql <= mid) optadd(o<<1, l, mid, ql, qr, d);
    if(mid < qr) optadd(o<<1|1, mid+1, r, ql ,qr, d);
    push_up(o, l, r);
}

int search(double key, double *x, int n) {//在
    int l = 0, r = n - 1;
    while(l <= r) {
        int mid = (l + r) >> 1;
        if(x[mid] == key) return mid;
        if(x[mid] > key) r = mid - 1;
        else l = mid + 1;
    }
    return -1;//也可以不写,但这样逻辑上过得去 
}

int main() {
    double x1, x2, y1, y2;
    int num = 0;
    while(cin>>n , n) {
        int k = 0;
        for(int i = 0; i < n; i++) {
            cin>>x1>>y1>>x2>>y2;
            hack[k] = x1;
            s[k++] = seg(x1, x2, y1, 1);
            hack[k] = x2;
            s[k++] = seg(x1, x2, y2, -1);
        }
        sort(s, s+k);
        sort(hack, hack+k);//离散化 
        int tot = 0;
        for(int i = 1; i < k; i++) if(hack[i] != hack[tot]) hack[++tot] = hack[i];
        double ans = 0;
        //cnt 会在这清零 
        tot++;//tot在上面是从零开始数组的坐标,现在是大小 
        for(int i = 0; i < k; i++) {//开始扫描 
            int L = search(s[i].l , hack, tot);
            int R = search(s[i].r , hack, tot) - 1;//左闭右开
            optadd(1, 0, tot-1, L, R, s[i].d );//querysum操作,顺便更新底边长度和底边相差个数 
            ans += sumv[1] * (s[i+1].h - s[i].h );//新增面积 
        }
        printf("Test case #%d\nTotal explored area: %.2lf\n\n",++num,ans);
    }
}/*
这里注意下扫描线段时r-1:int R=search(s[i].l,hack,m)-1;
计算底边长时r+1:if(mark[n])sum[n]=hack[right+1]-hack[left];
解释:假设现在有一个线段左端点是l=0,右端点是r=m-1
则我们去更新的时候,会算到sum[1]=hack[mid]-hack[left]+hack[right]-hack[mid+1]
这样的到的底边长sum是错误的,why?因为少算了mid~mid+1的距离,由于我们这利用了
离散化且区间表示线段,所以mid~mid+1之间是有长度的,比如hack[3]=1.2,hack[4]=5.6,mid=3
所以这里用r-1,r+1就很好理解了 
*/

扫描法+线段树

原文:https://www.cnblogs.com/tyner/p/11231256.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!