二维数点 & 扫描线

对扫描线不熟悉到不怎么会打了QAQ。

二维数点

这是一个很简单的板块,今天来讲讲进阶的玩法。

他的本质其实是通过树状数组或其他数据结构对于形如 \(l_i \le x \le l_j,r_i \le y \le r_j\)的式子进行加速!

例题:P10281 [USACO24OPEN] Grass Segments G

这里给出两个解法

  1. cdq 分治

这个方法有点无脑。

对于 \((l_i, r_i, k_i)\) ,有 \((l_j, r_j)\) 满足条件当且仅当:

\(k_i \le \min(r_i,r_j) - \max(l_i,l_j)\)

那么把他拆开就是:

\[\begin{cases} k_i \le r_i - l_i \\ k_i \le r_i - l_j \\ k_i \le r_j - l_i \\ k_i \le r_j - l_j \end{cases} \]

那么显然第一个式子完全是成立的。

所以就剩下了:

\[\begin{cases} k_i \le r_i - l_j \\ k_i \le r_j - l_i \\ k_i \le r_j - l_j \end{cases} \]

在整理一下变为:

\[\begin{cases} l_j \le r_i - k_i \\ r_j \ge l_i + k_i \\ r_j - l_j \ge k_i \end{cases} \]

我们发现条件好像无法直接维护,考虑新建 \(n\) 个虚点,其 k 的值为 \(r_j−l_j\),此时按照 \(k\) 的值从小到大进行排序即可。

此时我们要求的是右区间对左区间的贡献,考虑左区间按照 \(l_i+k\) 从大到小排序,右区间按照 \(r_j\) 从大到小排序,每次走指针时将满足条件的虚点的 \(l_j\) 加入树状数组,最后对于每一个实点,求出 \([1,r_i−k]\) 内的点的数量即可。

时间复杂度为:\(O(N \log^2N)\)

2.二维数点方法

\(\min(r_i,r_j) \ge k_i+\max(l_i,l_j)\)

分类讨论,如果 \(r_j \ge r_i\),那么\(\min(r_i,r_j)=r_i\)

那么 \(r_i \ge k_i+\max(l_i,l_j)\),因为已经有 \(r_i \ge k_i+l_i\) 恒成立,所以得到第一种情况:

\(r_j \ge r_i \ge l_j+k_i\),整理后得到 \(r_j \ge r_i,l_j \le r_i-k_i\)

如果 \(r_j \lt r_i\),那么 \(r_j \ge k_i+\max(l_i,l_j)\),此时 \(r_j \ge k_i+l_i\)\(r_j \ge k_i+l_j\)
整理后得到第二种情况:\(r_j \ge l_i+k_i,r_j \lt r_i,r_j - l_j \ge k_i\)

情况一:\(r_j \ge r_i,l_j \le r_i-k_i\)

情况二:\(r_j \ge l_i+k_i,r_j \lt r_i,r_j - l_j \ge k_i\)

显然这两种情况不能同时满足,我们发现只用分别维护 \(2\) 个二维偏序即可。

时间复杂度: \(O(N \log N)\)

扫描线

其实这类题大部分只是扫描线的思想但不用写。

其实本质上也就是差分/扫描线。

先来一个简单的例题吧

P9478 [NOI2023] 方格染色

注意到这道题其实斜线最多只有五条且斜率一样,但是斜线也有可能有重叠,所以要先处理。

然后我们发现横线与竖线我们可以直接按照扫描线去扫,而斜线就直接加就行了。

可是斜线可能与横线或竖线重叠,所以要记录所有重复的点并去重最后减掉就行。

戳我喵~

```
int n, m, q;
int x, x2, y, y2;
struct Tree {
	int l, r, c;
	int sum, fl, fr;
} tree[N << 2];

struct node {
	int x, y, y2;
	int f;
} line[N << 1];

struct slush {
	int x, y, xx, yy;
}edge[N];

int s[N];

bool cmp(node a, node b) {
	return a.x < b.x;
}

void build(int l, int r, int rt) {
	tree[rt].l = l, tree[rt].r = r, tree[rt].c = tree[rt].sum = 0;
	tree[rt].fl = s[l], tree[rt].fr = s[r];
	if (l + 1 == r) return;
	int mid = l + r >> 1;
	build(l, mid, rt << 1);
	build(mid, r, rt << 1 | 1);
}

void calc(int rt) {
	if (tree[rt].c > 0) {
		tree[rt].sum = tree[rt].fr - tree[rt].fl;
		return;
	}
	if (tree[rt].l + 1 == tree[rt].r) tree[rt].sum = 0;
	else tree[rt].sum = tree[rt << 1].sum + tree[rt << 1 | 1].sum;
}

void update(int rt, int ql, int qr, int val) {
	if (ql <= tree[rt].l && tree[rt].r <= qr) {
		tree[rt].c += val;
		calc(rt);
		return;
	}
	int mid = (tree[rt].l + tree[rt].r) >> 1;
	if (ql < mid) update(rt << 1, ql, qr, val);
	if (qr > mid) update(rt << 1 | 1, ql, qr, val);
	calc(rt);
}

signed main() {
	int HYWHYWHYWHYWHYWHYWHYW = re;
	n = re, m = re, q = re;
	int tot = 0, cnt = 0;
	for (int i = 1; i <= q; i++) {
		int op = re;
		if (op == 3) edge[++cnt] = {re, re, re, re};
		else {
			x = re, y = re, x2 = re + 1, y2 = re + 1;
			line[++tot].x = x, line[tot].y = y, line[tot].y2 = y2;
			line[tot].f = 1;
			s[tot] = y;
			line[++tot].x = x2, line[tot].y = y, line[tot].y2 = y2;
			line[tot].f = -1;
			s[tot] = y2;
		}
	}
	bool flag = 0;
	while (!flag) {
		flag = 1;
		for (int i = 1; i <= cnt; i++) for (int j = 1; j <= cnt; j++) {
			if (i == j) continue;
			if (edge[i].x - edge[i].y == edge[j].x - edge[j].y && max(edge[i].y, edge[j].y) <= min(edge[i].yy, edge[j].yy)) {
				edge[i] = {min(edge[i].x, edge[j].x), min(edge[i].y, edge[j].y), max(edge[i].xx, edge[j].xx), max(edge[i].yy, edge[j].yy)}, flag = 0;
				edge[j] = {0, 0, 0, 0};
				swap(edge[j], edge[cnt]), cnt--;
			}
		}
	}
	set<pii> point;
	int ans = 0;
	for (int i = 1; i <= cnt; i++) ans += edge[i].yy - edge[i].y + 1;
	for (int i = 1; i <= cnt; i++) for (int j = 1; j <= tot; j += 2) {
		if (line[j].x + 1 == line[j + 1].x) {
			int diff = line[j].x - edge[i].x, new_y = edge[i].y + diff;
			if (diff >= 0 && new_y >= line[j].y && new_y <= line[j].y2 - 1 && new_y <= edge[i].yy) point.insert({line[j].x, new_y});
		}
		else {
			int diff = line[j].y - edge[i].y, new_x = edge[i].x + diff;
			if (diff >= 0 && new_x >= line[j].x && new_x <= line[j + 1].x - 1 && new_x <= edge[i].xx) point.insert({new_x, line[j].y});
		}
	}
	ans -= point.size();
	sort(s + 1, s + 1 + tot);
	int KKK = unique(s + 1, s + 1 + tot) - (s + 1);
	sort(line + 1, line + 1 + tot, cmp);
	build(1, KKK, 1);
	for (int i = 1; i <= tot; i++) line[i].y = lower_bound(s + 1, s + 1 + KKK, line[i].y) - s, line[i].y2 = lower_bound(s + 1, s + 1 + KKK, line[i].y2) - s;
	update(1, line[1].y, line[1].y2, line[1].f);
	for (int i = 2; i <= tot; i++) {
		ans += tree[1].sum * (line[i].x - line[i - 1].x);
		update(1, line[i].y, line[i].y2, line[i].f);
	}
	wr(ans), endl;
}
```
</details>
posted @ 2026-08-13 20:10  OiLight  阅读(15)  评论(0)    收藏  举报