象限四分树浅谈 / LibreOJ #6016 Solution
象限四分树浅谈 / LibreOJ #6016 崂山白花蛇草水 Solution
0. 前言
在做这道题前,我并没有接触过 k-D tree. 直到我运用我在 AtCoder Heuristic Contest 030 中想到的一种不依靠任何额外信息的猜测方法 AC 之后,我在搜索引擎上查找该方法,才知道我所采用的方法叫做象限四分树。
1. 方法引入 / Polyomino Mining
本题的大致题面为,给出一块 \(N\times N\) 油田中的几个油田子连通块的形状,但是不确定其位置。可以通过两种询问确定位置,一种是询问几个节点中出现被油田子连通块覆盖的次数总和(带有正态分布扰动),另一种是询问单个节点中有没有油。需要在尽量减少对少量节点中油量进行查询次数的情况下找出所有有油的节点。
由于我实力太弱,无法想出利用油田子连通块的形状进行优化的方法,故我直接丢弃了这个信息。
于是我开始思考如何在不适用这个信息的情况下尽可能避开没有油的节点。
于是我猜想,若选出一些节点进行查询后返回的油量为 \(0\) ,那么这些节点内大概率都没有油,可以放弃查询。否则可以继续查找。所以我想到将整块油田分割成四个象限进行查找。这种方法确实可以大大减少在误差不高的情况下对无油节点的查询次数。
2. 象限四分树
一个平面直角坐标系有四个象限,分别为右上,左上,左下,右下。而在一个二维平面上,我们同样可以使用象限方法进行分割。这种方法所建出的树结构与线段树类似,但是一棵象限四分树上的非叶子节点有四个子节点,分别代表四个象限。也就是说,这种树的深度仍然为 \(O(\log n)\) 。
该数据结构可以解决子平面上最值、平面面积并/交等问题。
同样的,也可以解决下面的问题。
3. LibreOJ #6016 崂山白花蛇草水
按照上面的方法建象限四分树,但是需要动态构建,否则会超出空间限制。不过对于本题,每个树上的节点是一个 vector
,用以存储该节点所代表的子平面中所有的崂山百花蛇草水的瓶数。
插入时,遍历所有包含该位置的树上节点,并在里面插入对应的瓶数。
查询时,二分答案寻找大小符合的瓶数即可。
不过有一个优化。
由于查询时多次寻找被完全包含的树上节点并不划算,我们可以仅通过一次提取提取出所有被包含的节点,并在之后直接在这些节点上进行二分查找,时间会被缩短很多。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 5e5 + 10, L = 1, R = 1e9;
using vi = vector<int>;
using vpi = vector<vi *>;
vpi trx;
int n, m, la;
struct st {
int lx, rx, ly, ry;
vi ele;
st *lu, *ru, *ld, *rd;
void update(int x, int y, int z) {
// fprintf(stderr, "%d %d %d %d\n", lx, rx, ly, ry);
ele.insert(upper_bound(ele.begin(), ele.end(), z), z);
if (lx == rx and ly == ry)
return;
int mid1 = (lx + rx) >> 1, mid2 = (ly + ry) >> 1;
if (x <= mid1) {
if (y <= mid2) {
if (!lu)
lu = new st{lx, mid1, ly, mid2, {}, nullptr, nullptr, nullptr, nullptr};
lu->update(x, y, z);
return;
}
if (!ru)
ru = new st{lx, mid1, mid2 + 1, ry, {}, nullptr, nullptr, nullptr, nullptr};
ru->update(x, y, z);
return;
}
if (y <= mid2) {
if (!ld)
ld = new st{mid1 + 1, rx, ly, mid2, {}, nullptr, nullptr, nullptr, nullptr};
ld->update(x, y, z);
return;
}
if (!rd)
rd = new st{mid1 + 1, rx, mid2 + 1, ry, {}, nullptr, nullptr, nullptr, nullptr};
rd->update(x, y, z);
}
void extract(int xlb, int xrb, int ylb, int yrb) {
// fprintf(stderr, "%d %d %d %d\n", lx, rx, ly, ry);
if (lx >= xlb and ly >= ylb and rx <= xrb and ry <= yrb) {
trx.push_back(&ele);
return;
}
int mid1 = (lx + rx) >> 1, mid2 = (ly + ry) >> 1;
if (xlb <= mid1) {
if (ylb <= mid2 and lu)
lu->extract(xlb, xrb, ylb, yrb);
if (yrb > mid2 and ru)
ru->extract(xlb, xrb, ylb, yrb);
}
if (xrb > mid1) {
if (ylb <= mid2 and ld)
ld->extract(xlb, xrb, ylb, yrb);
if (yrb > mid2 and rd)
rd->extract(xlb, xrb, ylb, yrb);
}
}
} tr;
int ls(int x) {
int res = 0;
for (auto &i : trx) {
res += i->end() - upper_bound(i->begin(), i->end(), x);
}
return res;
}
int query(int x) {
int tmp = 0;
for (auto &i : trx)
tmp += i->size();
// fprintf(stderr, "%d\n", trx.size());
if (tmp < x)
return 0;
int l = L, r = R, mid = 0;
while (l < r) {
mid = (l + r) >> 1;
if (ls(mid) >= x)
l = mid + 1;
else
r = mid;
}
return l;
}
int main() {
scanf("%d%d", &n, &m);
tr.lx = tr.ly = 1, tr.rx = tr.ry = n;
for (int i = 1, a, b, c, d, e, f; i <= m; i++) {
scanf("%d%d%d%d", &a, &b, &c, &d);
b ^= la, c ^= la, d ^= la;
if (a & 1) {
tr.update(b, c, d);
continue;
}
scanf("%d%d", &e, &f);
e ^= la, f ^= la;
trx.clear();
tr.extract(b, d, c, e);
la = query(f);
if (!la)
puts("NAIVE!ORZzyz.");
else
printf("%d\n", la);
}
}