K - D Tree
K - D Tree
- \(K - Dimensional ~ Tree\),下可简称 \(KDT\)
- 是 最优的 \(K\) 维正交范围 修改查询 的数据结构
- 支持 矩形加,矩形求和,等 二维平面 上的范围操作
- 可以拓展到 \(K\) 维,时间复杂度为 \(O(N ^ {\frac {k - 1} {k}} + \log N)\)
注意到,\(K - D ~ Tree\) 只有在做 确定的正交范围操作 时复杂度才 可能是正确的
否则在 极端情况 下 复杂度可能会卡到 \(O (N ^ K)\) 级别(如 平面最近最远点对)
\(KDT\) 的 建树及维护 可以采用类似 平衡树 的 每个节点存值,保留子树信息 的方式
也可以采用类似 线段树 的 叶子节点存值,保留儿子信息 的方式
第二种情况的 相当于一个动态开点线段树,空间是 \(O(2 N)\) 的,但是好写
所以下文中的 代码实现 均采取 第二种方式(板子题给出了两种实现)
Luogu P4475 巧克力王国
\(K - D ~ Tree\) 板子题,但是 复杂度...
这种 二维范围查询,不是确定矩形 的情况下,时间复杂度 是不对的(可以卡到 \(O (N ^ 2)\))
但是据称在 随机数据下 复杂度仍然可以保证 \(O(N \sqrt N)\),具体证明 没有
我们维护 \(N\) 个 \((x, y)\) 二元组
以 \(x, y\) 两维 交替 取 中位数 建出 \(KDT\),具体实现可以看代码
这一步的时间复杂度是 \(O (N \log N)\) 的
然后我们维护出 子树 / 儿子 的 \(Max_{x, y}, Min_{x, y}\) 信息,显然存在下面的关系
若 \(a > 0, b > 0\),结点 \(u\) 的 \(a Max_x + b Max_y < c\)
则 结点代表的 所有二元组 均满足 \(ax + by < c\),即 可以被接受
反之当 \(a Min_x + b Min_y \ge c\) 时,该结点代表的 所有二元组 均 不可被接受
若 \(a < 0, b > 0\),结点 \(u\) 的 \(a Min_x + b Max_y < c\)
则 结点代表的 所有二元组 均满足 \(ax + by < c\),即 可以被接受
反之同理,然后 \(b < 0\) 的情况同理
注意到 上述条件 均为 充分条件 而非 充要条件
即该节点代表的二元组中 不一定真正存在 \((Max_x, Max_y)...\) 这些二元组
换言之,即使 实际上 这个结点的所有二元组都符合 \(ax + by < c\)
它也有可能不符合上面的 \(a Max_x + b Max_y < c\) 的条件,从而将 进入递归
对于 一次询问,\(a, b\) 确定,于是一个结点只有 三种可能的情况
即 上面两种(结点二元组 全 接受 / 不可接受)或 结点部分二元组被接受
前两种情况可以 \(O(1)\) 判断后返回,后面一种情况直接 递归到儿子 即可
容易发现,对于 叶子节点,\(Max_x, Max_y, Min_x, Min_y\)
就是其代表的 唯一二元组 的 \(x, y\),于是上述 充分条件 就变成了 充要条件
所以 最终答案一定会被统计完全,不会遗漏
具体实现参考代码
类线段树版,即 仅叶子存储原始值
#include <bits/stdc++.h>
const int MAXN = 50005;
const int INF = 1e9;
using namespace std;
unsigned int Dim = 0;
struct node {
int d[2], v;
inline bool operator < (const node &a) const {
return d[Dim] < a.d[Dim];
}
} V[MAXN];
int N, Q;
namespace KD_Tree {
struct Node {
int lc, rc;
int Max[2], Min[2];
long long Sum;
} T[MAXN << 1];
#define LC T[x].lc
#define RC T[x].rc
#define M ((L + R) >> 1)
int Cnt = 0;
inline void Maintain (const int x) {
T[x].Max[0] = max (T[LC].Max[0], T[RC].Max[0]);
T[x].Max[1] = max (T[LC].Max[1], T[RC].Max[1]);
T[x].Min[0] = min (T[LC].Min[0], T[RC].Min[0]);
T[x].Min[1] = min (T[LC].Min[1], T[RC].Min[1]);
T[x].Sum = T[LC].Sum + T[RC].Sum;
}
inline int Build (const int L, const int R, int x = 0, const int d = 0) {
Dim = d, nth_element (V + L, V + M, V + R);
if (!x) T[x = ++ Cnt] = {0, 0, 0, 0, INF, INF, 0};
if (L == R) return T[x].Min[0] = T[x].Max[0] = V[L].d[0], T[x].Min[1] = T[x].Max[1] = V[L].d[1], T[x].Sum = V[L].v, x;
LC = Build (L, M, LC, !d), RC = Build (M + 1, R, RC, !d), Maintain (x);
return x;
}
inline long long Query (const int a, const int b, const long long c, const int x = 1) {
if (!x) return 0;
long long Max = 0, Min = 0;
if (a > 0) Max += a * T[x].Max[0], Min += a * T[x].Min[0];
if (a < 0) Max += a * T[x].Min[0], Min += a * T[x].Max[0];
if (b > 0) Max += b * T[x].Max[1], Min += b * T[x].Min[1];
if (b < 0) Max += b * T[x].Min[1], Min += b * T[x].Max[1];
if (Max < c) return T[x].Sum;
if (Min >= c) return 0;
return Query (a, b, c, LC) + Query (a, b, c, RC);
}
}
int a, b, c;
int main () {
#ifndef USE_FASTIO
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#endif
cin >> N >> Q;
for (int i = 1; i <= N; ++ i)
cin >> V[i].d[0] >> V[i].d[1] >> V[i].v;
KD_Tree::Build (1, N);
for (int i = 1; i <= Q; ++ i)
cin >> a >> b >> c, cout << KD_Tree::Query (a, b, c) << '\n';
return 0;
}
类平衡树版,即 所有节点均存储原始值
#include <bits/stdc++.h>
const int MAXN = 50005;
const int INF = 1e9;
using namespace std;
inline bool Typ;
struct Choc {
int Pos[2], h;
inline bool operator < (const Choc &a) const {
return Pos[Typ] < a.Pos[Typ];
}
} C[MAXN];
int N, Q;
namespace KD_Tree {
struct Node {
int lc, rc;
int Pos[2], Max[2] = {0, 0}, Min[2] = {INF, INF};
long long Val, Sum;
} T[MAXN];
int tot = 0, rt = 0;
#define LC T[x].lc
#define RC T[x].rc
#define M ((L + R) >> 1)
inline void Maintain (const int x) {
T[x].Min[0] = min ({T[x].Pos[0], T[LC].Min[0], T[RC].Min[0]});
T[x].Min[1] = min ({T[x].Pos[1], T[LC].Min[1], T[RC].Min[1]});
T[x].Max[0] = max ({T[x].Pos[0], T[LC].Max[0], T[RC].Max[0]});
T[x].Max[1] = max ({T[x].Pos[1], T[LC].Max[1], T[RC].Max[1]});
T[x].Sum = T[LC].Sum + T[RC].Sum + T[x].Val;
}
inline int Build (const int L, const int R, int x = 0, const bool d = 0) {
if (L > R) return 0;
if (!x) x = ++ tot;
Typ = d, nth_element (C + L, C + M, C + R + 1);
T[x].Val = T[x].Sum = C[M].h;
T[x].Pos[0] = T[x].Max[0] = T[x].Min[0] = C[M].Pos[0];
T[x].Pos[1] = T[x].Max[1] = T[x].Min[1] = C[M].Pos[1];
if (L == R) return x;
LC = Build (L, M - 1, LC, !d), RC = Build (M + 1, R, RC, !d);
Maintain (x);
return x;
}
inline long long Que (const int a, const int b, const int c, const int x = 1) {
if (!x) return 0;
long long Max = 0, Min = 0;
Max += a < 0 ? 1ll * a * T[x].Min[0] : 1ll * a * T[x].Max[0];
Max += b < 0 ? 1ll * b * T[x].Min[1] : 1ll * b * T[x].Max[1];
Min += a < 0 ? 1ll * a * T[x].Max[0] : 1ll * a * T[x].Min[0];
Min += b < 0 ? 1ll * b * T[x].Max[1] : 1ll * b * T[x].Min[1];
if (Max < c) return T[x].Sum;
if (Min >= c) return 0;
return (1ll * a * T[x].Pos[0] + 1ll * b * T[x].Pos[1] < c) * T[x].Val + Que (a, b, c, LC) + Que (a, b, c, RC);
}
}
int a, b, c;
int main () {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> N >> Q;
for (int i = 1; i <= N; ++ i)
cin >> C[i].Pos[0] >> C[i].Pos[1] >> C[i].h;
KD_Tree::Build (1, N);
for (int i = 1; i <= Q; ++ i)
cin >> a >> b >> c, cout << KD_Tree::Que (a, b, c) << '\n';
return 0;
}
Luogu P3710 方方方的数据结构
一个复杂度正确的 \(KDT\) 板子
注意到 多维护一个时间维
修改操作 在时间维上即是 开始操作 到 撤销操作 的 时间区间
我们 离线操作,把 修改操作 的 时间区间求出
于是变成了 矩形乘,矩形加,单点查,\(KDT\) 维护 查询操作对应点 即可
时间复杂度 \(O (N \sqrt N)\)
以下代码可能写的是 矩形求和,问题不大,常数可能大了 \(EPS\)
#include <bits/stdc++.h>
const int MAXN = 150005;
const int INF = 1e9;
const int MOD = 998244353;
using namespace std;
bool Dim = 0;
struct Opt {
int opt, l, r, u, d, x;
} O[MAXN];
struct Que {
int d[2];
inline bool operator < (const Que &a) const {
return d[Dim] < a.d[Dim];
}
} P[MAXN];
int tot = 0;
namespace KD_Tree {
struct Node {
int lc, rc;
int Min[2], Max[2], Siz;
long long Mul = 1, Add = 0;
long long Sum;
} T[MAXN << 1];
#define LC T[x].lc
#define RC T[x].rc
#define M ((L + R) >> 1)
int Cnt;
inline void Maintain (const int x) {
T[x].Max[0] = max (T[LC].Max[0], T[RC].Max[0]);
T[x].Max[1] = max (T[LC].Max[1], T[RC].Max[1]);
T[x].Min[0] = min (T[LC].Min[0], T[RC].Min[0]);
T[x].Min[1] = min (T[LC].Min[1], T[RC].Min[1]);
T[x].Sum = (T[LC].Sum + T[RC].Sum) % MOD;
}
inline void PutAdd (const int x, const int add) {
T[x].Sum = (T[x].Sum + 1ll * T[x].Siz * add) % MOD;
T[x].Add = (T[x].Add + add) % MOD;
}
inline void PutMul (const int x, const int mul) {
T[x].Sum = T[x].Sum * mul % MOD;
T[x].Mul = T[x].Mul * mul % MOD;
T[x].Add = T[x].Add * mul % MOD;
}
inline void Update (const int x) {
if (T[x].Mul != 1) PutMul (LC, T[x].Mul), PutMul (RC, T[x].Mul), T[x].Mul = 1;
if (T[x].Add != 0) PutAdd (LC, T[x].Add), PutAdd (RC, T[x].Add), T[x].Add = 0;
}
inline int Build (const int L, const int R, int x = 0, const int d = 0) {
Dim = d, nth_element (P + L, P + M, P + R + 1);
if (!x) T[x = ++ Cnt] = {0, 0, INF, INF, 0, 0, R - L + 1, 1, 0, 0};
if (L == R) return T[x].Min[0] = T[x].Max[0] = P[L].d[0], T[x].Min[1] = T[x].Max[1] = P[L].d[1], x;
LC = Build (L, M, LC, !d), RC = Build (M + 1, R, RC, !d), Maintain (x);
return x;
}
inline bool InRange (const int L, const int R, const int U, const int D, const int x) {
return L <= T[x].Min[0] && T[x].Max[0] <= R && U <= T[x].Min[1] && T[x].Max[1] <= D;
}
inline bool OtRange (const int L, const int R, const int U, const int D, const int x) {
return L > T[x].Max[0] || T[x].Min[0] > R || U > T[x].Max[1] || T[x].Min[1] > D;
}
inline void Add (const int L, const int R, const int U, const int D, const int v, const int x = 1) {
if (!x) return ;
if (OtRange (L, R, U, D, x)) return ;
if (InRange (L, R, U, D, x)) return PutAdd (x, v);
Update (x), Add (L, R, U, D, v, LC), Add (L, R, U, D, v, RC), Maintain (x);
}
inline void Mul (const int L, const int R, const int U, const int D, const int v, const int x = 1) {
if (!x) return ;
if (OtRange (L, R, U, D, x)) return ;
if (InRange (L, R, U, D, x)) return PutMul (x, v);
Update (x), Mul (L, R, U, D, v, LC), Mul (L, R, U, D, v, RC), Maintain (x);
}
inline long long Sum (const int L, const int R, const int U, const int D, const int x = 1) {
if (!x) return 0;
if (OtRange (L, R, U, D, x)) return 0;
if (InRange (L, R, U, D, x)) return T[x].Sum;
Update (x); return Sum (L, R, U, D, LC) + Sum (L, R, U, D, RC);
}
}
using namespace KD_Tree;
int N, Q;
int main () {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> N >> Q;
for (int i = 1; i <= Q; ++ i) {
cin >> O[i].opt;
if (O[i].opt <= 2) cin >> O[i].l >> O[i].r >> O[i].x, O[i].u = i;
if (O[i].opt == 3) cin >> O[i].l, O[i].u = i, P[++ tot] = {O[i].l, O[i].u};
if (O[i].opt == 4) cin >> O[i].x, O[O[i].x].d = i;
}
Build (1, tot);
for (int i = 1; i <= Q; ++ i) {
if (O[i].opt == 1 && O[i].d == 0) O[i].d = Q;
if (O[i].opt == 2 && O[i].d == 0) O[i].d = Q;
}
for (int i = 1; i <= Q; ++ i) {
if (O[i].opt == 1) Add (O[i].l, O[i].r, O[i].u, O[i].d, O[i].x);
if (O[i].opt == 2) Mul (O[i].l, O[i].r, O[i].u, O[i].d, O[i].x);
}
for (int i = 1; i <= Q; ++ i) {
if (O[i].opt == 3) cout << Sum (O[i].l, O[i].l, O[i].u, O[i].u) << '\n';
}
return 0;
}