二维数点进阶

二维数点进阶

基本类型

根据有无更改,是否在线,以及聚合运算性质,可以分出来很多种

接下来用 \([(x_1,y_1),(x_2,y_2)]\) 表示左上角为 \((x_1, y_1)\),右下角为 \((x_2,y_2)\)(我习惯放到第四象限讨论

1. 静态,离线,存在逆元

经典问题,由于离线,可以按照某一维排完序后增添相应的扫描线,然后将线上的信息记录,这里推荐使用树状数组,常数小

查询 \([(x_1,y_1),(x_2,y_2)]\) 可以转成查询 \([(0,0),(x_2,y_2)]\)\([(0,0),(x_1-1,y_2)]\)\([(0,0),(x_2,y_1-1)]\)\([(0,0),(x_1-1,y_1-1)]\) 这四个从原点出发的查询

2. 静态,在线,存在逆元

要求在线,我们就可以使用可持久化线段树记录历史状态,查询依然不变

3. 矩形操作 + 单点查询

考虑静态,离/在线,存在逆元

还是一样的,只不过反了过来,对于矩阵操作,根据二维差分的操作,依然可以拆成四个从 \((0,0)\) 出发的矩形区域

单点就很简单了

4. 静态,离/在线,不存在逆元

这个时候树状数组就是不行的了,除非查询 \(x_i\le X,y_i\le Y\) 这般的

  • 对于 \(x_i\le X, l_i \le y_i \le r_i\) 这种可以用线段树, \(\mathcal O(\log n)\)

  • 对于更一般的 \(x_i \in[L_i, R_i], y\in [l_i,r_i]\) 这种,线段树和树状数组就解决不了了,要用到树套树等高级方法,\(\mathcal O(\log^2 n)\)

    一般是不会这么考的,出现这种情况,很大可能是建模建错了

5. 动态,离线,存在逆元

类似于线段树分治,我们将每一个点的出现起止时间都记录下来,然后就会变成静态的三维数点问题,并非我们讨论范畴,所以不惜将

6. 矩形操作,矩形查询

二维差分,考虑树状数组是如何做到区修区查的

定义 \(d_{ij}\)\(a_{ij}\) 的差分数组,所以 \(a_{xy} = \sum\limits_{i = 1}^{x}\sum\limits_{j=1}^{y}\)

所以前缀和就可以表示成:

\[\begin{equation} \begin{split} S_{x,y} &= \sum_{i=1}^{x}\sum_{j=1}^{y}\sum_{k = 1}^{i}\sum_{u = k}^j d_{ku}\\ &= \sum_{i=1}^x\sum_{j=1}^y(x-i+1)(y-j+1)d_{ij}\\ &=(x+1)(y+1)\sum_{i=1}^x\sum_{j=1}^yd_{ij}-(x+1)\sum_{i=1}^{x}\sum_{j=1}^{y}id_{ij}-(y+1)\sum_{i=1}^{x}\sum_{j=1}^y jd_{ij}+\sum_{i=1}^{x}\sum_{j=1}^{y}ijd_{ij} \end{split} \end{equation} \]

观察最后的式子,不难发现,我们需要维护 \(d_{ij}\),$ id_{ij}\(,\)jd_{ij}\(,\)ijd_{ij}$

但是这里千万不可以使用树状数组,光是开一个二维的本地栈空间就爆了

7. 斜线

这种没学,鸽着

实践 & 建模技巧

二维数点的版子你是会了,可是什么时候用呢?

例题

BZOJ 2874 训练士兵

就是类型六,要用可持久化线段树,并且注意什么时候改用离散化后的值,什么时候用原值

AT_abc233_h [ABC233Ex] Manhattan Christmas Tree

容易发现直接做第 \(k\) 大的话十分困难,所以我们可以思考如何不做第 \(k\)

我们想一想第 \(k\) 大怎么出现的,那就是距离小于它的恰好有 \(k- 1\) 个,诶,这个恰好就限定了范围,可是我们并不知道真正的第 \(k\) 大的距离

不知道是可以猜的,就像二分答案,可以二分出来一个距离,然后看这个距离划分出区域内点有多少

但是,由于是曼哈顿距离,到某个点的距离为定值的点构成了一个正方形,还是旋转了 \(45^\circ\) 的正方形

既然旋转了,那就转回来,对于点 \((x,y)\),旋转 \(45^\circ\) 后就变成了 \((x+y,x-y)\) (这里曼哈顿距离在旋转后就变成了切比雪夫距离,即 \(\max(|x+y|,|x-y|) = |x|+|y|\)

对应的,由 \((x,y)\) 变成 \(\left(\frac{x+y}{2}, \frac{x-y}{2}\right)\),就可以从切比雪夫距离变成曼哈顿距离,即 \(\max(|x|,|y|) = \frac{|x+y|+|x-y|}{2}\)

这样子,这个正方型就摆正了,就可以进行二维数点了

View Code
bool check(int distance, int k, int x, int y) {
	int x1 = x - distance, x2 = x + distance;
	int y1 = y - distance, y2 = y + distance;

	if (x1 < 0) x1 = 0;
	if (x2 > 200000) x2 = 200000;
	if (y1 < 1) y1 = 1;
	if (y2 > 400000) y2 = 400000;
	
	if (x1 > x2 || y1 > y2) return false;
	
	int cnt = query(root[x2], 1, 400000, y1, y2) - 
	(x1 > 0 ? query(root[x1 - 1], 1, 400000, y1, y2) : 0);
	return cnt >= k;
}

void solve() {
	cin >> n;
	
	for (int i = 1; i <= n; i++) {
		int x, y;
		cin >> x >> y;
		p[i].x = x + y;
		p[i].y = x - y + 200001;
	}
	
	sort(p + 1, p + n + 1);
	
	root[0] = 0;
	int j = 1;
	for (int i = 0; i <= 200000; i++) {
		if (i > 0) root[i] = root[i - 1];
		else root[i] = 0;
		
		while (j <= n && p[j].x == i) {
			root[i] = update(root[i], 1, 400000, p[j].y, 1);
			j++;
		}
	}
	
	cin >> q;
	
	for (int i = 1; i <= q; i++) {
		int a, b, k;
		cin >> a >> b >> k;
		
		int x = a + b;
		int y = a - b + 200001;
		
		int l = 0, r = 200000;
		while (l < r) {
			int mid = (l + r) >> 1;
			if (check(mid, k, x, y)) r = mid;
			else l = mid + 1;
		}
		cout << l << "\n";
	}
}

P10281 [USACO24OPEN] Grass Segments G

分类讨论

首先要使得覆盖的长度超过 \(k_i\),这一个区间的长度也就必须超过 \(k_i\)

我们可以先将条件都写出来:

  • \(l_j \le r_i - k_i\)
  • \(r_j \ge l_i+k_i\)

这两个条件不可以同时满足

先考虑 \(l_j\le r_i - k_i\),这个时候 \(r_j \ge r_i\)

考虑 \(r_j \ge l_i+k_i\),防止数重,再加上一个条件 \(r_j \lt r_i\),这个时候 \(r_j - l_j \ge k_i\)

这个时候就转化成了两个二维数点,一个对于二元组 \((l,r)\),另一个针对 \((r,r-l)\)

涉及两个二维数点,实现稍有些复杂

最后需要注意一点,对于 \((l,r)\) 的二维数点,有可能会将 \((l_i,r_i)\) 给算进去,最后要减掉

P13094 [FJCPC 2025] 帕累托前沿

直接暴力求是肯定过不了的

观察题目给出的条件,就可以发现,满足该条件的数肯定是该区间内的最大值,并且唯一

所以我们稍微改一下最大的定义:\(a = \max S \iff \nexists k \in S \setminus \{a\},k\ge a\)

这个时候,我们发现,由于相同的值会阻碍彼此成为最大值,所以唯一的问题被解决了

定义 \(R_i\) 表示是的 \(x_i\) 能成为最大值的最大区间,\(l_i\) 是其左边界,\(r_i\) 是右边界(这里拿 \(x\) 举例,\(y\) 同理

这个时候,问题转化成了求使得 \(l_i \le ql_i \le i\le qr_i \le r_i\) 的个数,这就变成了二维数点

但是 \(R_i\) 怎么求出来呢?

构建一个区间最大值的线段树,在上面做线段树二分,找到在 \(i\) 前面最后一个 \(\ge x_i\) 以及在 \(i\) 后面第一个 \(\ge x_i\) 的位置

P11364 [NOIP2024] 树上查询

我何德何能居然能做noipT4了,Σ(⊙▽⊙"a

这就是前面两道题的融合版,只不过有一个性质比较难推而已

先把这个性质写出来,等会儿证明:

\[\text{depth}_{\operatorname{lca}^*(l,r)} = \min_{l\le i \lt r} \text{depth}_{\operatorname{lca}(i,i+1)} \]

题目要求 \(\max\limits_{l\le l'\le r'\le r \land r'-l'+1\ge k}\text{dep}_ {\text{lca}^*(l', r')}\)

即所有/子区间 LCA 的深度最大值

看到这个 \(\min\) 再结合上一道题,我们就会发现完全可以沿用上一题的方法,预处理 \(R_i\)

这个时候就多出了许多 \(R\),题目又要求子区间的长度 \(\ge k\),也就是这些 \(R\)\((l,r)\) 的交的长度必须大于等于 \(k\),诶,这不就是上上道题吗?

所以这么就做完了

Proof

这里用 \(E\) 表示 Euler 序,\(D\) 表示对应的深度,用 \(f\) 表示第一次进入的时间戳

首先,依据 RMQLCA,可以知道:

\[\text{depth}_{\operatorname{lca}(u,v)} = \min_{f[u] \le t \lt f[v]} D(t) \]

然后考虑将 \(\operatorname{lca}^*\) 改成我们熟悉的样子:假设 \(a\)\([l,r]\)\(f\) 最小的,\(b\) 则是最大的

那么

\[\operatorname{lca}^*(l,r) = \operatorname{lca}(a,b) \]

为方便,不妨设 \(a = l\)\(b = r\)

所以,转化为证明:

\[\text{depth}_{\operatorname{lca}(l,r)} = \min_{l\le i \lt r} \text{depth}_{\operatorname{lca}(i,i+1)} \]

将区间 \([f[l],f[r]]\) 按照相邻点分割

\[[f[l],f[r]]= \bigcup_{i=l}^{r-1}[f[i],f[i+1]] \]

所以

\[\min_{[f[l],f[r]]} D = \min _{i=1}^{r-1}\min_{[f[i], f[i+1]]}D \]

\[\min_{[f[i],f[i+1]]}D = \text{depth}(\operatorname{lca}(i,i+1)) \]

带入即得

posted @ 2026-08-10 21:17  Yangyihao  阅读(8)  评论(0)    收藏  举报