线段树扫描线

扫描线一般运用在图形上面,它和它的字面意思十分相似,就是一条线在整个图上扫来扫去,它一般被用来解决图形面积,周长等问题。     -------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;
}
posted @ 2020-03-22 00:45  Mcggvc  阅读(374)  评论(0编辑  收藏  举报