线段树扫描线
Posted mcggvc
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树扫描线相关的知识,希望对你有一定的参考价值。
扫描线一般运用在图形上面,它和它的字面意思十分相似,就是一条线在整个图上扫来扫去,它一般被用来解决图形面积,周长等问题。 -------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;
}
扫描线求矩形周长
方法类似求面积,离散化处理,线段树区间修改和查询方法同上。
统计答案: 读入一个四元组时,先记录当前的查询的值,进行修改后再查询一次,答案增加的量为两次查询的差的绝对值。 证明方法显然,这里不多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;
}
以上是关于线段树扫描线的主要内容,如果未能解决你的问题,请参考以下文章
优先队列 + 并查集 + 字典树 + 树状数组 + 线段树 + 线段树点更新 + KMP +AC自动机 + 扫描线