K-D Tree
K-D Tree
k-D Tree 是一种可以高效处理 \(k\) 维空间信息的数据结构,形态为 BST,树上的每个点都对应 \(k\) 维空间内的一个点,每个子树对应一个 \(k\) 维的超长方体。
实现
建树
- 若当前超长方体中只有一个点,返回这个点。
- 选择一个维度,将当前超长方体按照这个维度分成两个超长方体。
- 在选择的维度上以中位数为划分点,将点集划分为左右两部分。
- 将选择的点作为这棵子树的根节点,递归构建左右子树。
一般来说,k-D Tree 的构建划分维度不能仅用一维,这可以被该维相近但另一维差距较大的数据在后续操作中卡掉,通常采用交替建树或方差建树。
交替建树
交替选择维度建树。
int build(int l, int r, int tp) {
if (l > r)
return 0;
int mid = (l + r) >> 1;
nth_element(id + l, id + mid, id + r + 1, [&](const int &a, const int &b) {
return tp ? p[a].x < p[b].x : p[a].y < p[b].y;
});
int x = id[mid];
lc[x] = build(l, mid - 1, tp ^ 1), rc[x] = build(mid + 1, r, tp ^ 1);
return pushup(x), x;
}
方差建树
选择方差较大的一维建树。
int build(int l, int r) {
if (r < l)
return 0;
int mid = (l + r) >> 1;
double avrx = 0, avry = 0;
for (int i = l; i <= r; ++i)
avrx += p[id[i]].x, avry += p[id[i]].y;
avrx /= r - l + 1, avry /= r - l + 1;
double sx = 0, sy = 0;
for (int i = l; i <= r; ++i) {
sx += (double)(avrx - p[id[i]].x) * (avrx - p[id[i]].x);
sy += (double)(avry - p[id[i]].y) * (avry - p[id[i]].y);
}
nth_element(id + l, id + mid, id + r + 1, [&](const int &a, const int &b) {
return sx > sy ? p[a].x < p[b].x : p[a].y < p[b].y;
});
int x = id[mid];
lc[x] = build(l, mid - 1), rc[x] = build(mid + 1, r);
return pushup(x), x;
}
时间复杂度 \(O(n \log n)\) ,空间复杂度 \(O(n)\) 。
插入/删除
根号重构
插入的时候,先存下来要插入的点,每 \(B\) 次插入进行一次重构。
删除采用懒惰删除,打标记即可。
修改复杂度均摊 \(O(\frac{n}{B} \log n)\) ,查询 \(O(B + n^{1 - \frac{1}{k}})\) ,若修改、查询数量同阶则 \(B = O(\sqrt{n \log n})\) 最优,复杂度为 \(O(\sqrt{n \log n}) - O(\sqrt{n \log n} + n^{1 - \frac{1}{k}})\) 。
二进制分组
考虑维护若干棵 \(2\) 的自然数次幂的 k-D Tree,满足这些树的大小之和为 \(n\) 。
插入的时候,新增一棵大小为 \(1\) 的 k-D Tree,然后不断将相同大小的树的点拿出来,最后重构。
容易发现需要合并的树的大小一定从 \(2^0\) 开始且指数连续。复杂度类似二进制加法,是均摊 \(O(n \log^2 n)\) 的(重构本身带 \(\log\) )。
查询的时候,直接分别在每颗树上查询,复杂度为 \(O(\sum_{i \ge 0} (\frac{n}{2^i})^{1 - \frac{1}{k}}) = O(n^{1 - \frac{1}{k}})\) 。
应用
邻值查询
k-D Tree 在二维邻值查询中表现较为优秀,随机数据下单次查询复杂度为 \(O(\log n)\) ,但是仍能构造数据卡到单次 \(O(n)\) ,需要一些卡常。
P7883 平面最近点对(加强加强版)
给定 \(n\) 个二维平面上的点,求最近点对距离的平方值。
\(n \le 4 \times 10^5\)
首先按方差构建 k-D Tree,然后对每个点查询与其最近的点,利用 \(l, r\) 的极值可以进行有效剪枝,但是无法通过。
考虑启发式搜索,若两个子树都有可能成为答案,相对优的子树中查询,设计的估价函数为点到子树对应的长方形的最近距离。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll inf = 1e18;
const int N = 4e5 + 7;
struct Point {
int x, y;
} p[N];
int id[N], lc[N], rc[N], lx[N], rx[N], ly[N], ry[N];
ll ans = inf;
int n, root;
template <class T = int>
inline T read() {
char c = getchar();
bool sign = (c == '-');
while (c < '0' || c > '9')
c = getchar(), sign |= (c == '-');
T x = 0;
while ('0' <= c && c <= '9')
x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return sign ? (~x + 1) : x;
}
inline void pushup(int x) {
lx[x] = rx[x] = p[x].x, ly[x] = ry[x] = p[x].y;
if (lc[x]) {
lx[x] = min(lx[x], lx[lc[x]]), rx[x] = max(rx[x], rx[lc[x]]);
ly[x] = min(ly[x], ly[lc[x]]), ry[x] = max(ry[x], ry[lc[x]]);
}
if (rc[x]) {
lx[x] = min(lx[x], lx[rc[x]]), rx[x] = max(rx[x], rx[rc[x]]);
ly[x] = min(ly[x], ly[rc[x]]), ry[x] = max(ry[x], ry[rc[x]]);
}
}
int build(int l, int r) {
if (r < l)
return 0;
int mid = (l + r) >> 1;
double avrx = 0, avry = 0;
for (int i = l; i <= r; ++i)
avrx += p[id[i]].x, avry += p[id[i]].y;
avrx /= r - l + 1, avry /= r - l + 1;
double sx = 0, sy = 0;
for (int i = l; i <= r; ++i) {
sx += (double)(avrx - p[id[i]].x) * (avrx - p[id[i]].x);
sy += (double)(avry - p[id[i]].y) * (avry - p[id[i]].y);
}
nth_element(id + l, id + mid, id + r + 1, [&](const int &a, const int &b) {
return sx > sy ? p[a].x < p[b].x : p[a].y < p[b].y;
});
int x = id[mid];
lc[x] = build(l, mid - 1), rc[x] = build(mid + 1, r);
return pushup(x), x;
}
inline ll dist(int x, int y) {
return 1ll * (p[x].x - p[y].x) * (p[x].x - p[y].x) + 1ll * (p[x].y - p[y].y) * (p[x].y - p[y].y);
}
void query(int x, int k) {
if (x != k)
ans = min(ans, dist(x, k));
auto f = [&](int x) {
if (!x)
return inf;
ll res = 0;
if (lx[x] > p[k].x)
res += 1ll * (lx[x] - p[k].x) * (lx[x] - p[k].x);
if (rx[x] < p[k].x)
res += 1ll * (rx[x] - p[k].x) * (rx[x] - p[k].x);
if (ly[x] > p[k].y)
res += 1ll * (ly[x] - p[k].y) * (ly[x] - p[k].y);
if (ry[x] < p[k].y)
res += 1ll * (ry[x] - p[k].y) * (ry[x] - p[k].y);
return res;
};
ll fl = f(lc[x]), fr = f(rc[x]);
if (fl < fr) {
if (fl < ans) {
query(lc[x], k);
if (fr < ans)
query(rc[x], k);
}
} else {
if (fr < ans) {
query(rc[x], k);
if (fl < ans)
query(lc[x], k);
}
}
}
signed main() {
n = read();
for (int i = 1; i <= n; ++i)
p[i].x = read(), p[i].y = read();
iota(id + 1, id + n + 1, 1), root = build(1, n);
for (int i = 1; i <= n; ++i)
query(root, id[i]);
printf("%lld", ans);
return 0;
}
P4357 [CQOI2016] K 远点对
给定 \(n\) 个二维平面上的点,求第 \(k\) 远点对距离的平方值。
\(n \le 10^5\)
为了方便将 \(k\) 翻倍,这样任意点对都可以无需特判地计算两次。
维护一个小根堆,表示当前最远的 \(k\) 组点对的距离,那么每一次只有超过当前第 \(k\) 远的点对才会对优先队列产生影响。每次搜到的距离得到大于堆顶元素的距离时,用它代替堆顶,最后剩下的堆顶就是答案。
具体估价函数与上一题的类似的,每次优先遍历距离较远的子树。
由于本题数据经过构造,k-D Tree 无法通过最后一个 Hack 数据。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll inf = 1e18;
const int N = 1e5 + 7;
struct Point {
int x, y;
} p[N];
priority_queue<ll, vector<ll>, greater<ll> > q;
int id[N], lc[N], rc[N], lx[N], rx[N], ly[N], ry[N];
ll ans = inf;
int n, k, root;
template <class T = int>
inline T read() {
char c = getchar();
bool sign = (c == '-');
while (c < '0' || c > '9')
c = getchar(), sign |= (c == '-');
T x = 0;
while ('0' <= c && c <= '9')
x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return sign ? (~x + 1) : x;
}
inline void pushup(int x) {
lx[x] = rx[x] = p[x].x, ly[x] = ry[x] = p[x].y;
if (lc[x]) {
lx[x] = min(lx[x], lx[lc[x]]), rx[x] = max(rx[x], rx[lc[x]]);
ly[x] = min(ly[x], ly[lc[x]]), ry[x] = max(ry[x], ry[lc[x]]);
}
if (rc[x]) {
lx[x] = min(lx[x], lx[rc[x]]), rx[x] = max(rx[x], rx[rc[x]]);
ly[x] = min(ly[x], ly[rc[x]]), ry[x] = max(ry[x], ry[rc[x]]);
}
}
int build(int l, int r) {
if (r < l)
return 0;
int mid = (l + r) >> 1;
double avrx = 0, avry = 0;
for (int i = l; i <= r; ++i)
avrx += p[id[i]].x, avry += p[id[i]].y;
avrx /= r - l + 1, avry /= r - l + 1;
double sx = 0, sy = 0;
for (int i = l; i <= r; ++i) {
sx += (double)(avrx - p[id[i]].x) * (avrx - p[id[i]].x);
sy += (double)(avry - p[id[i]].y) * (avry - p[id[i]].y);
}
nth_element(id + l, id + mid, id + r + 1, [&](const int &a, const int &b) {
return sx > sy ? p[a].x < p[b].x : p[a].y < p[b].y;
});
int x = id[mid];
lc[x] = build(l, mid - 1), rc[x] = build(mid + 1, r);
return pushup(x), x;
}
inline ll dist(int x, int y) {
return 1ll * (p[x].x - p[y].x) * (p[x].x - p[y].x) + 1ll * (p[x].y - p[y].y) * (p[x].y - p[y].y);
}
void query(int x, int k) {
ll d = dist(x, k);
if (d > q.top())
q.pop(), q.emplace(d);
auto f = [&](int x) {
if (!x)
return 0ll;
int dx = max(p[k].x - lx[x], rx[x] - p[k].x), dy = max(p[k].y - ly[x], ry[x] - p[k].y);
return 1ll * dx * dx + 1ll * dy * dy;
};
ll fl = f(lc[x]), fr = f(rc[x]);
if (fl > fr) {
if (fl > q.top()) {
query(lc[x], k);
if (fr > q.top())
query(rc[x], k);
}
} else {
if (fr > q.top()) {
query(rc[x], k);
if (fl > q.top())
query(lc[x], k);
}
}
}
signed main() {
n = read(), k = read() * 2;
for (int i = 1; i <= n; ++i)
p[i].x = read(), p[i].y = read();
iota(id + 1, id + 1 + n, 1), root = build(1, n);
for (int i = 1; i <= k; ++i)
q.emplace(0);
for (int i = 1; i <= n; ++i)
query(root, i);
printf("%lld", q.top());
return 0;
}
P4475 巧克力王国
给出平面上的 \(n\) 个点 \((x_{1 \sim n}, y_{1 \sim n})\) ,每个点有一个权值 \(h_i\) 。
\(m\) 次查询,每次给出 \(a, b, c\) ,求所有满足 \(a x_i + b y_i < c\) 的点权和。
\(n \le 50000\) ,保证数据随机
使用二维 k-D Tree 对二维数点坐标极值进行维护,在查询时进行剪枝。
可以发现:
若 \(\max (ax + by)\) 都满足条件,那么整棵树都满足条件,可以直接加上。
若 \(\min(ax + by)\) 都不满足条件,则无需遍历该子树。
子树中的 \(\max (ax + by)\) 和 \(\min(ax + by)\) 可以通过记录 \(x, y\) 的极值得到。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 5e4 + 7;
struct Point {
int x, y, k;
} p[N];
int n, m;
namespace KDT {
ll s[N];
int id[N], lc[N], rc[N], mnx[N], mxx[N], mny[N], mxy[N];
int root;
inline void pushup(int x) {
mnx[x] = mxx[x] = p[x].x, mny[x] = mxy[x] = p[x].y, s[x] = p[x].k;
if (lc[x]) {
mnx[x] = min(mnx[x], mnx[lc[x]]), mxx[x] = max(mxx[x], mxx[lc[x]]);
mny[x] = min(mny[x], mny[lc[x]]), mxy[x] = max(mxy[x], mxy[lc[x]]);
s[x] += s[lc[x]];
}
if (rc[x]) {
mnx[x] = min(mnx[x], mnx[rc[x]]), mxx[x] = max(mxx[x], mxx[rc[x]]);
mny[x] = min(mny[x], mny[rc[x]]), mxy[x] = max(mxy[x], mxy[rc[x]]);
s[x] += s[rc[x]];
}
}
int build(int l, int r, int tp) {
if (l > r)
return 0;
int mid = (l + r) >> 1;
nth_element(id + l, id + mid, id + r + 1, [&](const int &a, const int &b) {
return tp ? p[a].x < p[b].x : p[a].y < p[b].y;
});
int x = id[mid];
lc[x] = build(l, mid - 1, tp ^ 1), rc[x] = build(mid + 1, r, tp ^ 1);
return pushup(x), x;
}
ll query(int x, int a, int b, int c) {
if (!x)
return 0;
if (max(1ll * a * mnx[x], 1ll * a * mxx[x]) + max(1ll * b * mny[x], 1ll * b * mxy[x]) < c)
return s[x];
else if (min(1ll * a * mnx[x], 1ll * a * mxx[x]) + min(1ll * b * mny[x], 1ll * b * mxy[x]) >= c)
return 0;
return (1ll * a * p[x].x + 1ll * b * p[x].y < c ? p[x].k : 0) + query(lc[x], a, b, c) + query(rc[x], a, b, c);
}
} // namespace KDT
signed main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
scanf("%d%d%d", &p[i].x, &p[i].y, &p[i].k);
iota(KDT::id + 1, KDT::id + n + 1, 1), KDT::root = KDT::build(1, n, 1);
while (m--) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
printf("%lld\n", KDT::query(KDT::root, a, b, c));
}
return 0;
}
矩形查询
每次查询时,若查询矩形与当前子树矩形不交则退出,若完全包含当前子树矩形则直接返回总贡献。
矩形查询的 k-D Tree 如果采用交替建树,可以证明单次最坏复杂度是 \(O(n^{\frac{k - 1}{k}})\) 。
当 \(k = 2\) 时,复杂度为 \(O(q \sqrt{n})\) ,十分优秀。
对于某些题目,采用提前建树的 k-D tree,单次矩形修改/查询 \(O(\sqrt n)\) ,单次单点查询/修改 \(O(\log n)\),时间复杂度上界为 \(O(q\times \sqrt n+q\times \log n)\) ,在单点操作次数相对大于区间操作次数时可以获得比树套树优越的复杂度。
P4148 简单题
有一个 \(n \times n\) 的棋盘,初始每个格子均为 \(0\) 。\(m\) 次操作,操作有:
- 单点权值加。
- 矩形权值求和。
\(n \le 5 \times 10^5\) ,\(m \le 2 \times 10^5\) ,强制在线,ML = 20MB
强制在线卡掉了 CDQ 分治,ML = 20MB 卡掉了树套树,只能使用 k-D Tree。
考虑动态插入,维护每一棵树的大小、权值和、两维的极值,就可以进行有效剪枝。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 7;
struct Point {
int x, y, k;
};
namespace KDT {
Point p[N];
vector<int> root, id;
int lc[N], rc[N], lx[N], rx[N], ly[N], ry[N], s[N], siz[N];
int tot;
inline void pushup(int x) {
lx[x] = rx[x] = p[x].x, ly[x] = ry[x] = p[x].y, s[x] = p[x].k, siz[x] = 1;
if (lc[x]) {
lx[x] = min(lx[x], lx[lc[x]]), rx[x] = max(rx[x], rx[lc[x]]);
ly[x] = min(ly[x], ly[lc[x]]), ry[x] = max(ry[x], ry[lc[x]]);
s[x] += s[lc[x]], siz[x] += siz[lc[x]];
}
if (rc[x]) {
lx[x] = min(lx[x], lx[rc[x]]), rx[x] = max(rx[x], rx[rc[x]]);
ly[x] = min(ly[x], ly[rc[x]]), ry[x] = max(ry[x], ry[rc[x]]);
s[x] += s[rc[x]], siz[x] += siz[rc[x]];
}
}
int build(int l, int r, int tp) {
if (l > r)
return 0;
int mid = (l + r) >> 1;
nth_element(id.begin() + l, id.begin() + mid, id.begin() + r + 1, [&](const int &a, const int &b) {
return tp ? p[a].x < p[b].x : p[a].y < p[b].y;
});
int x = id[mid];
lc[x] = build(l, mid - 1, tp ^ 1), rc[x] = build(mid + 1, r, tp ^ 1);
return pushup(x), x;
}
void dfs(int x) {
if (!x)
return;
id.emplace_back(x), dfs(lc[x]), dfs(rc[x]);
}
inline void insert(Point k) {
p[++tot] = k, id = {tot};
int nowsiz = 1;
while (!root.empty() && siz[root.back()] == nowsiz)
dfs(root.back()), root.pop_back(), nowsiz <<= 1;
root.emplace_back(build(0, id.size() - 1, 1));
}
int query(int x, int xl, int xr, int yl, int yr) {
if (!x || rx[x] < xl || lx[x] > xr || ry[x] < yl || ly[x] > yr)
return 0;
else if (xl <= lx[x] && rx[x] <= xr && yl <= ly[x] && ry[x] <= yr)
return s[x];
return (xl <= p[x].x && p[x].x <= xr && yl <= p[x].y && p[x].y <= yr ? p[x].k : 0) +
query(lc[x], xl, xr, yl, yr) + query(rc[x], xl, xr, yl, yr);
}
inline int query(int xl, int xr, int yl, int yr) {
int res = 0;
for (int it : root)
res += query(it, xl, xr, yl, yr);
return res;
}
} // namespace KDT
signed main() {
int n, op, lstans = 0;
scanf("%d", &n);
while (~scanf("%d", &op)) {
if (op == 1) {
int x, y, k;
scanf("%d%d%d", &x, &y, &k);
x ^= lstans, y ^= lstans, k ^= lstans;
KDT::insert((Point){x, y, k});
} else if (op == 2) {
int xl, yl, xr, yr;
scanf("%d%d%d%d", &xl, &yl, &xr, &yr);
xl ^= lstans, yl ^= lstans, xr ^= lstans, yr ^= lstans;
printf("%d\n", lstans = KDT::query(xl, xr, yl, yr));
} else
break;
}
return 0;
}
P5471 [NOI2019] 弹跳
二维平面上有 \(n\) 个城市,第 \(i\) 个城市在 \((x_i, y_i)\) ,城市标号为 \(1 \sim n\) 。
有 \(m\) 个弹跳装置,第 \(i\) 个弹跳装置在标号 \(p_i\) 的点,表示从 \(p_i\) 出发,可以花费 \(t_i\) 的时间到达 \(x \in [L_i, R_i], y \in [D_i, U_i]\) 的城市。
求 \(1\) 到所有点的最短路。
\(n \le 70000\) ,\(m \le 1.5 \times 10^5\) ,ML = 125MB
直接用 k-D Tree 优化建图,边数是 \(O(m \sqrt{n})\) 级别,无法接受。
考虑不显式建图,开 \(2n\) 个点,\(1 \sim n\) 表示原来的点,\(n + 1 \sim 2n\) 表示 k-D Tree 上的子树。考虑 Dijkstra 算法求最短路,每次取出堆顶点 \(u\) 时:
- \(u \le n\) :用 \(u\) 处所有弹跳装置在 k-D Tree 上做类似矩形查询的操作,松弛若干子树和若干单点。注意子树标记无需下传,当前标记不小于原先标记时退出。
- \(u > n\) :直接暴力松弛子树内的所有点即可。
不难发现 k-D Tree 上的每个点只会被放入堆中一次(因为祖先到它的距离为 \(0\) ,因此只会被最小的祖先松弛),因此时间复杂度为 \(O(n \log n + m \sqrt{n})\) 。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int N = 7e4 + 7;
struct Point {
int x, y;
} p[N];
struct Device {
ll t;
int xl, xr, yl, yr;
};
vector<Device> device[N];
priority_queue<pair<ll, int> > q;
ll dis[N << 1];
int n, m, w, h;
namespace KDT {
int id[N], lc[N], rc[N], lx[N], rx[N], ly[N], ry[N];
int root;
inline void pushup(int x) {
lx[x] = rx[x] = p[x].x, ly[x] = ry[x] = p[x].y;
if (lc[x]) {
lx[x] = min(lx[x], lx[lc[x]]), rx[x] = max(rx[x], rx[lc[x]]);
ly[x] = min(ly[x], ly[lc[x]]), ry[x] = max(ry[x], ry[lc[x]]);
}
if (rc[x]) {
lx[x] = min(lx[x], lx[rc[x]]), rx[x] = max(rx[x], rx[rc[x]]);
ly[x] = min(ly[x], ly[rc[x]]), ry[x] = max(ry[x], ry[rc[x]]);
}
}
int build(int l, int r, int tp) {
if (l > r)
return 0;
int mid = (l + r) >> 1;
nth_element(id + l, id + mid, id + r + 1, [&](const int &a, const int &b) {
return tp ? p[a].x < p[b].x : p[a].y < p[b].y;
});
int x = id[mid];
lc[x] = build(l, mid - 1, tp ^ 1), rc[x] = build(mid + 1, r, tp ^ 1);
return pushup(x), x;
}
void maintain(int x, Device k) {
if (!x || rx[x] < k.xl || lx[x] > k.xr || ry[x] < k.yl || ly[x] > k.yr || dis[x + n] <= k.t)
return;
else if (k.xl <= lx[x] && rx[x] <= k.xr && k.yl <= ly[x] && ry[x] <= k.yr) {
dis[x + n] = k.t, q.emplace(-dis[x + n], x + n);
return;
}
if (k.xl <= p[x].x && p[x].x <= k.xr && k.yl <= p[x].y && p[x].y <= k.yr) {
if (dis[x] > k.t)
dis[x] = k.t, q.emplace(-dis[x], x);
}
maintain(lc[x], k), maintain(rc[x], k);
}
void dfs(int x, ll k) {
if (!x)
return;
if (dis[x] > k)
dis[x] = k, q.emplace(-dis[x], x);
dfs(lc[x], k), dfs(rc[x], k);
}
} // namespace KDT
using namespace KDT;
inline void Dijkstra(int S) {
memset(dis + 1, inf, sizeof(ll) * n * 2);
dis[S] = 0, q.emplace(-dis[S], S);
while (!q.empty()) {
auto c = q.top();
q.pop();
if (-c.first != dis[c.second])
continue;
int u = c.second;
if (u <= n) {
for (Device it : device[u])
it.t += dis[u], maintain(root, it);
} else
dfs(u - n, dis[u]);
}
}
signed main() {
scanf("%d%d%d%d", &n, &m, &w, &h);
for (int i = 1; i <= n; ++i)
scanf("%d%d", &p[i].x, &p[i].y);
iota(id + 1, id + n + 1, 1), root = build(1, n, 1);
for (int i = 1; i <= m; ++i) {
int x, t, l, r, d, u;
scanf("%d%d%d%d%d%d", &x, &t, &l, &r, &d, &u);
device[x].emplace_back((Device){t, l, r, d, u});
}
Dijkstra(1);
for (int i = 2; i <= n; ++i)
printf("%lld\n", dis[i]);
return 0;
}
树套 k-D Tree
P4848 崂山白花蛇草水
在一个 \(n \times n\) 的二维平面上,\(m\) 次操作,操作有:
- 插入一个点,权值给定。
- 矩形查权值第 \(k\) 大。
\(n \le 5 \times 10^5\) ,\(m \le 10^5\) ,强制在线
直接线段树套 k-D Tree 即可,时间复杂度 \(O(m \sqrt{m} \log V)\) 。
#include <bits/stdc++.h>
using namespace std;
const int N = 3e6 + 7;
struct Point {
int x, y;
};
int n, m;
namespace KDT {
Point p[N];
vector<int> root[N], id;
int lc[N], rc[N], lx[N], rx[N], ly[N], ry[N], siz[N];
int tot;
inline void pushup(int x) {
lx[x] = rx[x] = p[x].x, ly[x] = ry[x] = p[x].y, siz[x] = 1;
if (lc[x]) {
lx[x] = min(lx[x], lx[lc[x]]), rx[x] = max(rx[x], rx[lc[x]]);
ly[x] = min(ly[x], ly[lc[x]]), ry[x] = max(ry[x], ry[lc[x]]);
siz[x] += siz[lc[x]];
}
if (rc[x]) {
lx[x] = min(lx[x], lx[rc[x]]), rx[x] = max(rx[x], rx[rc[x]]);
ly[x] = min(ly[x], ly[rc[x]]), ry[x] = max(ry[x], ry[rc[x]]);
siz[x] += siz[rc[x]];
}
}
int build(int l, int r, int tp) {
if (l > r)
return 0;
int mid = (l + r) >> 1;
nth_element(id.begin() + l, id.begin() + mid, id.begin() + r + 1, [&](const int &a, const int &b) {
return tp ? p[a].x < p[b].x : p[a].y < p[b].y;
});
int x = id[mid];
lc[x] = build(l, mid - 1, tp ^ 1), rc[x] = build(mid + 1, r, tp ^ 1);
return pushup(x), x;
}
void dfs(int x) {
if (!x)
return;
id.emplace_back(x), dfs(lc[x]), dfs(rc[x]);
}
inline void insert(int x, Point k) {
p[++tot] = k, id = {tot};
int nowsiz = 1;
while (!root[x].empty() && siz[root[x].back()] == nowsiz)
dfs(root[x].back()), root[x].pop_back(), nowsiz <<= 1;
root[x].emplace_back(build(0, id.size() - 1, 1));
}
int ask(int x, int xl, int xr, int yl, int yr) {
if (!x || rx[x] < xl || lx[x] > xr || ry[x] < yl || ly[x] > yr)
return 0;
else if (xl <= lx[x] && rx[x] <= xr && yl <= ly[x] && ry[x] <= yr)
return siz[x];
return (xl <= p[x].x && p[x].x <= xr && yl <= p[x].y && p[x].y <= yr) +
ask(lc[x], xl, xr, yl, yr) + ask(rc[x], xl, xr, yl, yr);
}
inline int query(int x, int xl, int xr, int yl, int yr) {
int res = 0;
for (int it : root[x])
res += ask(it, xl, xr, yl, yr);
return res;
}
} // namespace KDT
namespace SMT {
int lc[N], rc[N];
int root, tot;
void insert(int &x, int nl, int nr, int p, Point k) {
if (!x)
x = ++tot;
KDT::insert(x, k);
if (nl == nr)
return;
int mid = (nl + nr) >> 1;
if (p <= mid)
insert(lc[x], nl, mid, p, k);
else
insert(rc[x], mid + 1, nr, p, k);
}
int query(int x, int nl, int nr, int k, int xl, int xr, int yl, int yr) {
if (!x)
return 0;
if (nl == nr)
return KDT::query(x, xl, xr, yl, yr) >= k ? nl : 0;
int mid = (nl + nr) >> 1, res = KDT::query(rc[x], xl, xr, yl, yr);
return k > res ? query(lc[x], nl, mid, k - res, xl, xr, yl, yr) : query(rc[x], mid + 1, nr, k, xl, xr, yl, yr);
}
} // namespace SMT
signed main() {
scanf("%d%d", &n, &m);
int lstans = 0;
while (m--) {
int op;
scanf("%d", &op);
if (op == 1) {
int x, y, k;
scanf("%d%d%d", &x, &y, &k);
x ^= lstans, y ^= lstans, k ^= lstans;
SMT::insert(SMT::root, 1, 1e9, k, (Point){x, y});
} else {
int xl, yl, xr, yr, k;
scanf("%d%d%d%d%d", &xl, &yl, &xr, &yr, &k);
xl ^= lstans, yl ^= lstans, xr ^= lstans, yr ^= lstans, k ^= lstans;
lstans = SMT::query(SMT::root, 1, 1e9, k, xl, xr, yl, yr);
if (lstans)
printf("%d\n", lstans);
else
puts("NAIVE!ORZzyz.");
}
}
return 0;
}