hdu1255覆盖的面积(线段树+扫描线)

题目描述:

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

 

 

 

 

Input
输入数据的第一行是一个正整数T(1<=T<=100),代表测试数据的数量.每个测试数据的第一行是一个正整数N(1<=N<=1000),代表矩形的数量,然后是N行数据,每一行包含四个浮点数,代表平面上的一个矩形的左上角坐标和右下角坐标,矩形的上下边和X轴平行,左右边和Y轴平行.坐标的范围从0到100000.

注意:本题的输入数据较多,推荐使用scanf读入数据.
 

 

Output
对于每组测试数据,请计算出被这些矩形覆盖过至少两次的区域的面积.结果保留两位小数.
 

 

Sample Input
2 5 1 1 4 2 1 3 3 7 2 1.5 5 4.5 3.5 1.25 7.5 4 6 3 10 7 3 0 0 1 1 1 0 2 1 2 0 3 1
 

 

Sample Output
7.63 0.00
思路:扫描线算法,用两个len表示区间内的两种覆盖情况,len[0]表示该区间内覆盖次数为1的长度,len[1]表示该区间内覆盖次数大于1的长度
具体就看代码注释
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5007;
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define ls(x) x<<1
#define rs(x) x<<1|1
struct p {
    double a, b, h;
    int f;
    p() {};
    p(double x, double y, double z, int q) :a(x), b(y), h(z), f(q) {};
    bool operator<(const p b)const {
        return h < b.h;
    }
}e[maxn];
double x[maxn];
int num;
struct node {
    int l, r, cnt;
    double len[2];
}tree[maxn<<2];
void build(int rt, int l, int r) {
    tree[rt].len[0] = tree[rt].len[1] = tree[rt].cnt = 0;
    tree[rt].l = l, tree[rt].r = r;
    if (l == r)return;
    int mid = (l + r) >> 1;
    build(lson);
    build(rson);
}
void push_up(int rt) {
    int l = tree[rt].l, r = tree[rt].r;
    //tree[rt].cnt就相当于懒标记,标记该区间是否被全部一次性覆盖
    if (tree[rt].cnt) {//如果该区域被全部覆盖,覆盖次数为1的长度就直接是区间内的长度
        tree[rt].len[0] = x[r + 1] - x[l];
    }
    else if (l == r) {//如果没被覆盖过又刚好是叶子节点位置
        tree[rt].len[0] = 0;
    }
    else {//如果该区域没有直接覆盖过,则覆盖长度就是两个孩子的覆盖长度的和
        tree[rt].len[0] = tree[ls(rt)].len[0] + tree[rs(rt)].len[0];
    }

    if (tree[rt].cnt > 1) {//如果该区间直接覆盖次数超过了一次,则直接就是区间长度
        tree[rt].len[1] = x[r + 1] - x[l];
    }
    else if (l == r) {//如果是叶子覆盖次数又没超过一次
        tree[rt].len[1] = 0;
    }
    else if (tree[rt].cnt == 1) {//如果不是叶子(有孩子)且被完全覆盖了一次,
    //则该区间覆盖次数超过两次的长度就是两个子区间覆盖次数超过一次的长度的和
        tree[rt].len[1] = tree[ls(rt)].len[0] + tree[rs(rt)].len[0];
    }
    else {//如果该区间没有被直接覆盖,则长度就是两个子区间的覆盖两次的长度的和
        tree[rt].len[1] = tree[ls(rt)].len[1] + tree[rs(rt)].len[1];
    }
}
void update(int rt,int L, int R,int f) {
    if (L <= tree[rt].l &&tree[rt].r <= R) {//是子区间
        tree[rt].cnt += f;//懒标记
        push_up(rt);//更新一下
        return;
    }
    int mid = (tree[rt].l + tree[rt].r) >> 1;
    if (L <= mid) {
        update(ls(rt),L, R, f);
    }
    if (R > mid) {
        update(rs(rt),L, R, f);
    }
    push_up(rt);
}
int main() {
    //freopen("test.txt", "r", stdin);
    int t; scanf("%d", &t);
    while (t--) {
        int n; scanf("%d", &n);
        num = 0;
        while (n--) {
            double x1, y1,x2,y2;
            scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
            e[++num] = { x1,x2,y1,1 };//下面边使得x1-x2区间覆盖次数+1
            x[num] = x1;
            e[++num] = { x1,x2,y2,-1 };//上面的边使得x1-x2区间覆盖次数-1
            x[num] = x2;//把所有x保存起来,然后离散化
        }
        sort(x + 1, x + num + 1);
        int cnt = unique(x + 1, x + num + 1) - x-1;//离散化
        sort(e + 1, e + num + 1);//按照出现高度拍个序
        build(1, 1, cnt);//初始化,把所有点的初始数据置零
        double ans = 0;
        for (int i = 1; i < num; i++) {//有num个y,num-1次计算
            int l = lower_bound(x + 1, x + cnt + 1, e[i].a) - x;
            int r = lower_bound(x + 1, x + cnt + 1, e[i].b) - x - 1;//这是自己规定的,区间l-r的长度是x[r+1]-x[l];
            update(1, l, r, e[i].f);
            ans += tree[1].len[1] * (e[i + 1].h - e[i].h);
            //每次更新后,ans+所有区间中覆盖次数超过一次的区间长度*(下一个高度与当前高度的差)
            //因为覆盖次数超过一次的区间的覆盖面积一定是包含了
            //(所有区间中覆盖次数超过一次的区间长度*(下一个高度与当前高度的差))这个结果的,
            //把样例按着程序走一遍就能懂了
        }
        printf("%.2lf\n", ans);
    }
    return 0;
}

 

posted @ 2021-03-26 21:57  cono奇犽哒  阅读(62)  评论(0)    收藏  举报