线段树扫描线

扫描线一般运用在图形上面,它和它的字面意思十分相似,就是一条线在整个图上扫来扫去,它一般被用来解决图形面积,周长等问题。     -------OI Wiki

扫描线求面积并

P5490 【模板】扫描线

如图,假设三个矩形,求其并集的面积。考虑将每个矩形处理成两条平行于 y 轴的线段,左边的线段标记为 +1 ,右边的线段标记为 -1 ,用一个四元组\((x, y_1, y_2, k)\)存下来,于是我们得到了这样一个图形:

按照四元组中 x 坐标由小到大排序,我们就可以分成一段一段地处理整个图形:

不同颜色表示一次处理的一段,此图形共可分成5段。
考虑每次处理的矩形,沿 x 轴的长度很容易知道,即当前四元组 x 减去上一个四元组 x 。我们要处理沿 y 轴的长度,维护一个序列:
1.若当前读到一个标记为 +1 的线段 ,就在序列上将这段区间 +1 ,反之 - 1。
2.每次统计时求序列上标记大于 0 的长度即可。
具体处理:
1.由于题目中数据较大,需要离散化,用一个数组(num[])映射到原坐标值。
2.用线段树维护序列(序列中第 i 项对应\([num[i], num[i + 1] ]\)这个区间),线段树中每个节点维护两个值:s 和 cnt ,cnt 表示此段被标记的值,s 表示这段上 cnt > 0 的长度。update时,若当前结点 cnt > 0,那么 s 值为整段的长度,即\(num[r + 1] - num[l]\),否则为两个子结点长度之和,因为每次查询我们只关心整段序列,即线段树根结点,所以这个区间修改不需要标记下传,。
3.读入一个四元组\((x, y_1, y_2, k)\)时,先统计答案,再将序列上\([y_1, y_2 - 1]\)这段的标记加上 k ,。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long lld;
const int N = 100005;
const int M = 1000005;
int n, tot = 0, num[M], maxn = 0;
lld ans = 0;
struct Tree {
    int cnt, s;
} e[M << 2];
struct node {
    int x, l, r, k;
} p[N << 1];
void update(int i, int l, int r) {
    if(e[i].cnt > 0) e[i].s = num[r + 1] - num[l];// l, r表示num[l]到num[r + 1]这一段
    else e[i].s = e[i << 1].s + e[i << 1 | 1].s;
}
void add(int i, int l, int r, int nl, int nr, int k) {
    if(l >= nl && r <= nr) {
        e[i].cnt += k; update(i, l, r);
        return ;
    }
    int mid = (l + r) >> 1;
    if(nl <= mid) add(i << 1, l, mid, nl, nr, k);
    if(nr > mid) add(i << 1 | 1, mid + 1, r, nl, nr, k);
    update(i, l, r);
}
int get() {
    return e[1].s;
}
void add1(int x1, int x2, int y1, int y2) {
    p[++tot].x = x1; p[tot].l = y1; p[tot].r = y2; p[tot].k = 1;
    p[++tot].x = x2; p[tot].l = y1; p[tot].r = y2; p[tot].k = -1;
    maxn = max(y1, max(y2, maxn));
}
int a[N], b[N], c[N], d[N];
vector <int> tmp;
bool cmp(node a, node b) {
    return a.x < b.x;
}
int main() {
//  freopen("data.in", "r", stdin);
    scanf("%d", &n);
    for(int i = 1, x1, x2, y1, y2; i <= n; i++) {
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        a[i] = x1, b[i] = x2, c[i] = y1, d[i] = y2;
        tmp.push_back(y1); tmp.push_back(y2);
    }
    sort(tmp.begin(), tmp.end());//离散化
    for(int i = 1; i <= n; i++) {
        int save1 = c[i], save2 = d[i];
        c[i] = lower_bound(tmp.begin(), tmp.end(), c[i]) - tmp.begin() + 1;
        d[i] = lower_bound(tmp.begin(), tmp.end(), d[i]) - tmp.begin() + 1;
        num[c[i]] = save1, num[d[i]] = save2;//映射原值
        add1(a[i], b[i], c[i], d[i]);//添加四元组
    }
    sort(p + 1, p + 1 + tot, cmp);//按照x排序
    add(1, 1, maxn, p[1].l, p[1].r - 1, p[1].k);//添加第一条线段
    for(int i = 2; i <= tot; i++) {
        ans = ans + (1ll * get() * (p[i].x - p[i - 1].x));//统计答案,长乘宽
        add(1, 1, maxn, p[i].l, p[i].r - 1, p[i].k);//修改序列
    }
    printf("%lld", ans);
    return 0;
}

扫描线求矩形周长

P1856 [USACO5.5]矩形周长Picture

方法类似求面积,离散化处理,线段树区间修改和查询方法同上。
统计答案: 读入一个四元组时,先记录当前的查询的值,进行修改后再查询一次,答案增加的量为两次查询的差的绝对值。 证明方法显然,这里不多bb。
细节:
1.这样每次只能统计平行于 y 轴的线段的周长和,我们需要将每个点的 x,y 坐标交换后再重复上述操作。
2.四元组排序时若 x 坐标相同,则 k = 1 的排在前面(先加上再减去对统计对答案没有影响,若是先减去在加上可能会导致答案偏大)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 5005;
const int M = 20015;
const int F = 10001;
int a[N], b[N], c[N], d[N], n, tot = 0;
int num[M << 2];
long long ans = 0;
struct node {
    int x, l, r, k;
} p[N << 1];
void add(int x1, int y1, int x2, int y2) {
    if(y1 > y2) swap(y1, y2);
    p[++tot].x = x1, p[tot].l = y1, p[tot].r = y2, p[tot].k = 1;
    p[++tot].x = x2, p[tot].l = y1, p[tot].r = y2, p[tot].k = -1;
}
struct tree {
    int s, cnt;
} e[M << 2];
vector <int> tmp, opt;
void update(int i, int l, int r) {
    if(e[i].cnt > 0) e[i].s = num[r + 1] - num[l];
    else e[i].s = e[i << 1].s + e[i << 1 | 1].s;
}
void add(int i, int l, int r, int nl, int nr, int k) {
    if(l >= nl && r <= nr) {
        e[i].cnt += k; update(i, l, r);
        return ;
    }
    int mid = (l + r) >> 1;
    if(nl <= mid) add(i << 1, l, mid, nl, nr, k);
    if(nr > mid) add(i << 1 | 1, mid + 1, r, nl, nr, k);
    update(i, l, r);
}
int get() {
    return e[1].s;
}
bool cmp(node a, node b) {
    if(a.x == b.x) return a.k > b.k; //k = 1排在前面
    return a.x < b.x;
}
void solve() {
    sort(p + 1, p + 1 + tot, cmp);
    for(int i = 1; i <= tot; i++) {
        long long per = get();
        add(1, 1, tot, p[i].l, p[i].r - 1, p[i].k);
        ans = (ans + abs(per - get()));
    }
}
int main() {
//  freopen("data.in", "r", stdin);
    scanf("%d", &n);
    for(int i = 1, x1, x2, y1, y2; i <= n; i++) {
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        a[i] = x1, b[i] = y1, c[i] = x2, d[i] = y2;
        tmp.push_back(y1); tmp.push_back(y2);
        opt.push_back(x1), opt.push_back(x2);
    }
    sort(tmp.begin(), tmp.end());
    for(int i = 1; i <= n; i++) {
        int save1 = b[i], save2 = d[i];
        b[i] = lower_bound(tmp.begin(), tmp.end(), b[i]) - tmp.begin() + 1;
        d[i] = lower_bound(tmp.begin(), tmp.end(), d[i]) - tmp.begin() + 1;
        num[b[i]] = save1, num[d[i]] = save2;
        add(a[i], b[i], c[i], d[i]);
    }
    solve();
    for(int i = 0; i < (M << 2); i++) e[i].s = e[i].cnt = 0;
    memset(num, 0, sizeof(num));
    tot = 0;
    sort(opt.begin(), opt.end());
    for(int i = 1; i <= n; i++) {
        int save1 = a[i], save2 = c[i];
        a[i] = lower_bound(opt.begin(), opt.end(), a[i]) - opt.begin() + 1;
        c[i] = lower_bound(opt.begin(), opt.end(), c[i]) - opt.begin() + 1;
        num[a[i]] = save1, num[c[i]] = save2;
        add(b[i], a[i], d[i], c[i]);
    }
    solve();
    printf("%lld", ans);
    return 0;
}

原文地址:https://www.cnblogs.com/mcggvc/p/12538960.html

时间: 2024-11-06 13:30:47

线段树扫描线的相关文章

【BZOJ】1382: [Baltic2001]Mars Maps (线段树+扫描线)

1382: [Baltic2001]Mars Maps Time Limit: 5 Sec  Memory Limit: 64 MB Description 给出N个矩形,N<=10000.其坐标不超过10^9.求其面积并 Input 先给出一个数字N,代表有N个矩形. 接下来N行,每行四个数,代表矩形的坐标. Output 输出面积并 Sample Input 2 10 10 20 20 15 15 25 30 Sample Output 225 本以为是傻逼题,没想到不容易啊- 线段树+扫描

BZOJ 4059 Cerc2012 Non-boring sequences 线段树+扫描线

题目大意:定义一个序列为[不无聊的]当且仅当这个序列的任意一个区间都存在一个数只出现过一次,给定一个序列,要求判断这个序列是否是[不无聊的] 定义lasti表示第i个元素上一次出现的位置(第一次出现则为0),nexti表示第i个元素下一次出现的位置(最后一次出现则为n+1),那么这个元素能成为某个区间仅出现一次的数,当且仅当这个区间的左端点在[lasti+1,i]之间,右端点在[i,nexti?1]之间 我们可以将区间的左右端点放在二维平面上,那么一个元素产生的贡献是一个矩形,我们要确定的是所有

HDU 4419 Colourful Rectangle --离散化+线段树扫描线

题意: 有三种颜色的矩形n个,不同颜色的矩形重叠会生成不同的颜色,总共有R,G,B,RG,RB,GB,RGB 7种颜色,问7种颜色每种颜色的面积. 解法: 很容易想到线段树扫描线求矩形面积并,但是如何维护每种颜色的长度着实让我伤透了脑筋.后来看了一位朋友的题解,才幡然醒悟. 开始想到了用二进制表示颜色,R用001表示,G用010表示,B用100表示.那么就可以用十进制1~7表示7种不同颜色了. 维护 cov[rt][1~3] 表示此区间内3种原色各有多少个, Len[rt][i]表示每种颜色的长

sgu316Kalevich Strikes Back(线段树+扫描线)

做法:总体想法是求出一个矩形的面积以及它所包含的矩形,然后用自己的面积减掉所包含的.主要问题是怎样求解它所包含的矩形. 因为是没有相交点的,可以利用扫描线的方法去做,类似染色,当前段如果是x色,也就是第x个矩形,那么再把他染成y颜色时,说明x包含y,而当扫到y的上边时,这一段又恢复到x色.标记一下被包含的矩形,记录所包含的矩形. 因为会有恢复染色操作,up需要时时更新,左儿子和右儿子一样颜色时就可以合并为一段. 1 ; 2 } 3 void down(int w) 4 { 5 if(s[w]!=

hdu1255 覆盖的面积 线段树-扫描线

矩形面积并 线段树-扫描线裸题 1 #include<stdio.h> 2 #include<string.h> 3 #include<algorithm> 4 #include<math.h> 5 using namespace std; 6 const int maxm=2e3+5; 7 const double eps=1e-5; 8 9 struct seg{ 10 double x,y1,y2; 11 int c; 12 bool operator

线段树+扫描线 HDOJ 5091 Beam Cannon

题目传送门 1 /* 2 题意:给出若干个点的坐标,用一个矩形去覆盖,问最多能覆盖几个点 3 线段树+扫描线:思路是先建一棵以[y, y + h]的树,左右儿子[x, x + w] 4 以这棵树为范围,从左到右扫描,更新点数,x的+1, x+w的-1(超过矩形范围) 5 ans = 每次更新时所覆盖点数的最大值 6 */ 7 #include <cstdio> 8 #include <algorithm> 9 #include <iostream> 10 #includ

POJ 3277 City Horizon(线段树+扫描线+离散化)

题目地址:POJ 3277 水题..稍微处理一下然后用求面积并的方法求即可. 代码如下: #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <stdlib.h> #include <math.h> #include <ctype.h> #include <queue> #include <

[POI 2001+2014acm上海邀请赛]Gold Mine/Beam Cannon 线段树+扫描线

Description Byteman, one of the most deserving employee of The Goldmine of Byteland, is about to retire by the end of the year. The Goldmine management would like to reward him in acknowledgment of his conscientious work. As a reward Byteman may rece

hdu1542 Atlantis (线段树+扫描线+离散化)

Atlantis Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 9032    Accepted Submission(s): 3873 Problem Description There are several ancient Greek texts that contain descriptions of the fabled i

HDU 1828 Picture 线段树+扫描线

题意:给你一些矩形的左上角点的坐标和右下角点的坐标,求周长并 最显而易见的思路就是对于x轴和y轴做两次扫描线,对于负数的坐标进行离散化.每次增加的值是线段变化量的绝对值.具体写法和求面积并的差不多. #include <cstdio> #include <algorithm> #include <cstring> #include <vector> using namespace std; #define lson rt << 1 , l , m