扫描线
正交范围
在一个 \(B\) 维平面直角坐标系下,第 \(i\) 维限制在整数 \([l_i,r_i]\) 间,形成的点集,叫做 \(B\) 维正交范围。
例题 \(1\)
给定 \(\{a_n\}\),\(m\) 次查询 \(a\) 的一个区间内有多少个值只出现过一次。
对于一个位置 \(i\),设它的前驱、后继位置分别是 \(p,q\)。那么对于 \(l\) 在 \((p,i]\),\(r\) 在 \([i,r)\) 的询问,\(i\) 会对这些询问产生 \(1\) 的贡献。
于是变成了:\(n\) 次矩形加,然后是 \(m\) 次单点查询。直接扫描线就行了。
区间子区间问题
多次询问某个区间内有多少个子区间满足某个条件。
以 \(l\) 为横轴,\(r\) 为纵轴建立平面直角坐标系,那么一个区间 \([l,r]\) 对应着坐标系中一个点。
画出来长这样:
假如我们要询问 \([L,R]\) 中有多少满足条件的子区间,那么就是询问图中的蓝色区域:
假如能保证所有 \(l>r\) 的位置都不会产生贡献,那询问的实际上是一个 2-side 矩形:
例题 \(2\)
给定一个长为 \(m\) 的操作序列,有三种操作:
- 区间赋值;
- 交换两个位置的值;
- 单点求值。
有 \(q\) 次询问,每次问初始将序列赋值成 \(0\),然后执行 \([l,r]\) 间的操作,得到的答案之和是多少。
扫右端点,每次在最后加一个操作是比较困难的。考虑倒着扫。
枚举左端点 \(l\),维护所有右端点的答案。对于每一个位置的询问开一个 vector,vector 内的元素表示这个询问的答案还是 \(0\)。当 \(l\) 左移一位时:
- 若新的操作是区间赋值,那就相当于把区间内的所有 vector 里的询问都定成这个值,然后清空这些 vector。对一个询问定值,会影响到包含这个询问的区间的答案。而这些区间构成了一个后缀,所以线段树维护即可。
- 交换:把两个 vector 交换一下;
- 查询:push_back。
这个是有均摊性质的:vector 内一共只会有 \(\mathcal{O}(m)\) 个元素,每个元素被删的时候会有 \(\mathcal{O}(\log m)\) 的复杂度。所以总复杂度是 \(\mathcal{O}((q+m)\log m)\)。