1 条题解
-
0
一、题意理解
1. 操作类型
有三种操作:
I x1 y1 x2 y2:新增一个领主,要占领矩形D x:删除第 个新增的领主(按I的顺序编号)Q x1 y1 x2 y2:查询矩形 与当前存在的所有领主矩形相交的数量
相交定义:两个矩形有公共点(即边界相接也算相交)。
二、核心问题
我们要支持动态插入、删除矩形,以及查询与给定矩形相交的矩形数量。
这是一个二维平面上的矩形相交计数问题,且支持删除操作。
三、问题转化
1. 矩形相交条件
两个矩形 和 相交的充要条件是:
$$x_1 \le x'_2 \quad\&\quad x'_1 \le x_2 \quad\&\quad y_1 \le y'_2 \quad\&\quad y'_1 \le y_2 $$换句话说,在 轴和 轴上的投影区间都要相交。
四、标准解法思路(容斥)
对于查询矩形 ,要统计与它相交的矩形数量 。
设总矩形数为 ,则 (与 不相交的矩形数)。
与 不相交的情况有四种:
- 矩形在 的左边:
- 矩形在 的右边:
- 矩形在 的下边:
- 矩形在 的上边:
但这些情况可能有重叠(矩形同时在左边和上边等),需要容斥。
但更常见的方法是:直接计算相交数 = 总数 - 全不相交数,全不相交的矩形满足:
$$x'_2 < x_1 \quad\text{或}\quad x'_1 > x_2 \quad\text{或}\quad y'_2 < y_1 \quad\text{或}\quad y'_1 > y_2 $$用容斥原理可以转化为计算一些偏序关系的数量。
五、代码中的巧妙做法
这个代码采用了一种更直接的方法:将相交条件转化为四维偏序问题。
设领主矩形为 ,查询矩形为 。
相交的条件 且 且 且 可以拆成 4 个不等式,每个不等式对应一个维度的比较。
我们可以统计不满足某个不等式的矩形数,然后容斥。但直接容斥 4 个条件很复杂。
代码实际采用了另一种思路:将每个矩形看作四维空间中的一个点 ,查询就是统计满足四维偏序关系的点数。
六、坐标离散化
由于坐标范围很大(无限平面),我们需要离散化:
for (int j = 1; j <= n; ++j) aux[j] = a[0][j]; for (int j = 1; j <= n; ++j) aux[j + n] = a[2][j]; sort(aux + 1, aux + 2 * n + 1); int cntf = unique(aux + 1, aux + 2 * n + 1) - aux - 1; for (int j = 1; j <= n; ++j) { a[0][j] = lower_bound(aux + 1, aux + cntf + 1, a[0][j]) - aux; } for (int j = 1; j <= n; ++j) { a[2][j] = lower_bound(aux + 1, aux + cntf + 1, a[2][j]) - aux; }对 分别离散化,坐标值变为 到 的整数。
七、分步统计
代码将相交矩形数 分解为:
$$N = \text{总数} - (\text{在左边的矩形} + \text{在右边的矩形} + \text{在下边的矩形} + \text{在上边的矩形}) + \text{重复扣除的} $$但实际上代码的做法是:
- 先假设所有矩形都与查询相交,即
ans[i] = cnt_ins - cnt_del(当前存在的矩形总数)。 - 然后减去那些一定不相交的矩形数。
一定不相交的四种情况:
- 在左边:
- 在右边:
- 在下边:
- 在上边:
7.1 第一轮统计
if (op[i] == 0) { // 查询 ans[i] += sum1[0] - query1<0>(a[2][i]); // 在左边:x2 < X1 ans[i] += sum1[1] - query1<1>(a[3][i]); // 在下边:y2 < Y1 ans[i] += query1<2>(a[0][i] - 1); // 在右边:x1 > X2 ans[i] += query1<3>(a[1][i] - 1); // 在上边:y1 > Y2 }这里
bit1[0]存储所有领主矩形的 值,sum1[0]是总数,sum1[0] - query1(a[2][i])统计 的矩形数(因为 是查询的 )。其他三个同理。
但这还不够,因为这样会重复扣除那些同时满足两个条件的矩形(如在左边且在下边),所以还需要加回这些重复的部分。
7.2 第二轮统计(处理重复扣除)
代码用四个树状数组
bit1[0..3]分别维护 的分布,但只是单维统计,无法处理两维组合的情况。为了解决二维组合(如左边且上边),代码采用了四维偏序的容斥:
对于查询 ,领主矩形 。
考虑 在 的左上方的情况:
- 在左边:
- 在上边:
这两个条件组合。
代码用四个向量数组
vec1, vec2, vec3, vec4来记录需要处理的二维查询和更新。
八、二维树状数组的替代方案
由于需要支持删除,且 ,直接二维树状数组或线段树内存太大。
代码采用了分治树状数组的技巧:
- 将二维条件 转化为:在 维度上树状数组,每个节点维护一个 的树状数组。
- 但这样空间 还是大。
所以代码实际上用了离线处理 + 按第一维排序的方法。
对于每个查询 ,要统计 且 的矩形数。
我们可以按 从小到大处理,维护当前 的所有矩形的 分布(用树状数组)。
具体做法:
- 将所有领主矩形的 和查询的 放在一起。
- 按第一维排序。
- 遇到领主矩形:在 树状数组中 +1。
- 遇到查询:查询 的数量。
但这需要处理动态插入和删除。代码中,领主矩形的插入和删除也看作“事件”,按 排序后一起处理。
九、代码结构解析
9.1 预处理
for (int i = 1; i <= n; ++i) { if (op[i]) { // 插入或删除 // 在 vec1, vec2, vec3, vec4 中记录事件 // 例如 vec1[x].emplace_back(v, op[i]) 表示在 x 处有事件 } else { // 查询 // 在 vec1, vec2, vec3, vec4 中记录查询 // 例如 vec1[x].emplace_back(v, i+1) 表示查询 } }这里
vec1[x]对应 维度,vec3[x]对应 (处理右边条件)。9.2 处理二维条件
for (int i = 1; i <= n * 2; ++i) { for (auto [u, v] : vec1[i]) { if (v > 1) { // 查询 ans[v - 1] += query2<0>(u); } else { // 更新 add2<0>(u, v); tmp0.push_back(u); } } // ... 类似处理 vec2, vec3, vec4 // 清空树状数组 }这里
query2<0>(u)查询 的数量(对应 )。通过这种方式,我们处理了四种二维组合:
- 且
- 且
- 且
- 且
这四种情况在第一轮统计中被重复扣除了,现在加回来。
十、算法总结
- 离散化坐标到 。
- 第一轮统计:用四个一维树状数组分别统计四种不相交情况(左、右、下、上)。
- 第二轮统计:离线处理四种二维组合条件(左上、左下、右上、右下),用一维树状数组按第一维排序后处理第二维。
- 容斥原理:最终相交数 = 总数 - 单边不相交数 + 双边组合数(因为单边统计时,双边情况被扣了两次,需要加回一次)。
时间复杂度:,由于常数小,可以通过 。
空间复杂度:。
十一、样例验证
样例:
5 I 1 1 3 3 Q 2 2 4 4 I 1 1 5 5 D 1 Q 2 3 3 3- 第一个查询时,只有矩形 (1,1)-(3,3),与查询矩形 (2,2)-(4,4) 相交,输出 1。
- 删除第一个矩形后,剩下矩形 (1,1)-(5,5),与查询 (2,3)-(3,3) 相交,输出 1。
算法正确。
这个解法是经典的四维偏序容斥 + 离线树状数组分治,巧妙地将二维矩形相交计数转化为多个低维偏序问题,适合处理 级别的动态问题。
>>
- 1
信息
- ID
- 6091
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 3
- 已通过
- 1
- 上传者