K-D tree \text{K-D tree} K-D tree 是一种对 k k k 维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。
主要应用于多维空间关键数据的搜索。
是二叉搜索树结构。
所以可以用来解决高维偏序问题,可以代替树套树以及 CDQ \text{CDQ} CDQ 分治(一般不容易写挂)。
k − d k-d k−d 树的构造是基于对 K K K 维空间的分割,每次选取其中一维坐标的中位数作为划分界线。
一般循环地以每维坐标为划分依据,把中位数所在点作为该子树的根, 动态开点。
以二维为例(可以展示图片):
时间复杂度 T ( n ) = O ( n ) + T ( n 2 ) = O ( n log n ) T(n)=O(n)+T(\frac n2)=O(n\log n) T(n)=O(n)+T(2n)=O(nlogn),深度为 O ( log n ) O(\log n) O(logn)。
多维空间存储用结构体,每次选用不同维度的坐标做划分参考,所以我们需要重载坐标点的偏序关系规则(大小比较方式)。
我习惯定义一个全局变量 dim \text{dim} dim,表示当前的维度编号。
bool operator < ( point a, point b ) {
if( dim == 0 ) return a.x < b.x;
if( dim == 1 ) return a.y < b.y;
}
可以用 C++ \text{C++} C++ 库里面自带的 nth_element()
O ( n ) O(n) O(n) 求中位数。
nth_element(g+l,g+mid,g+r+1,cmp)
:将 g g g 数组的 l ∼ r l\sim r l∼r 区间按照 c m p cmp cmp 方式比较大小,求出第 m i d mid mid 大的点信息。
这个函数只能保证 m i d mid mid 左边的都不大于 m i d mid mid, m i d mid mid 右边的都不小于 m i d mid mid,但不保证其内部仍然有序,即只排序了 m i d mid mid 这一个点。
我习惯重载结构体的大小符号比较,见上,所以就不用写后面的 c m p cmp cmp。
void build( int &now, int l, int r, int d ) {
now = ++ cnt, dim = d;
nth_element( g + l, g + mid, g + r + 1 );
//记录k-d tree上这个点的信息
t[now].Min = t[now].Max = t[now].v = g[mid];
t[now].dim = d, num[t[now].v.id] = now;
t[now].val = t[now].v.val;
if( l < mid ) build( lson, l, mid - 1, d ^ 1 );
if( mid < r ) build( rson, mid + 1, r, d ^ 1 );
pushup( now );
}
由此也可以建出 k k k 维的 k − d k-d k−d 树了。无非是维度循环的更改罢了。(d+1)%k
建树过程中根据题目要求进行信息选择记忆。最后的总结会写到。
与所有二叉搜索树一样, k − d k-d k−d 树也要进行自底向上的信息合并问题。
以二维为例(比较直观能感受)
蓝色是 k − d k-d k−d 树上的自编号,旁边黑色编号表示这个点存储的点的编号。
以根 1 1 1 为例,储存的是编号为 3 3 3 的黑点信息,通过下面给出的合并操作,你会发现其实在 1 1 1 点维护的是一个(Min.x,Min.y)-(Max.x,Max.y)
的矩形。
而这个 M i n . x / M i n . y Min.x/Min.y Min.x/Min.y 是由黑点 1 1 1 提供的, M a x . x Max.x Max.x 是由黑点 6 6 6 提供的, M a x . y Max.y Max.y 是由黑点 2 2 2 提供的,这些信息都是通过合并往上传递的。
在这里再阐述 k − d k-d k−d 树节点的结构体。
struct point { int x, y; };
struct node {
point Min, Max, v;
int lson, rson;
}t[maxn];
//Min即管辖矩形的左下角 Max即管辖矩形的右上角 v是这个k-d tree点储存的实点
void chkmin( point &a, point b ) {
a.x = min( a.x, b.x );
a.y = min( a.y, b.y );
}
void chkmax( point &a, point b ) {
a.x = max( a.x, b.x );
a.y = max( a.y, b.y );
}
void pushup( int now ) {
int lson = t[now].lson, rson = t[now].rson;
if( lson ) {
chkmin( t[now].Min, t[lson].Min );
chkmax( t[now].Max, t[lson].Max );
}
if( rson ) {
chkmin( t[now].Min, t[rson].Min );
chkmax( t[now].Max, t[rson].Max );
}
}
k k k 维无法想象出来,但是可以类比是维护了一个 k k k 维图形。
为了方便码,可以用将维度放到一个数组里面,循环维护。
void pushup( int now ) {
for( int i = 0;i < k;i ++ ) {
if( lson ) {
t[now].Min.d[i] = min( t[now].Min.d[i], t[lson].Min.d[i] );
t[now].Max.d[i] = max( t[now].Max.d[i], t[lson].Max.d[i] );
}
if( rson ) {
t[now].Min.d[i] = min( t[now].Min.d[i], t[rson].Min.d[i] );
t[now].Max.d[i] = max( t[now].Max.d[i], t[rson].Max.d[i] );
}
}
}
貌似看来,二维矩阵里面的计数问题,面积问题,什么的都可以做。甚至多维里面的计数也可以做!
这里的合并只是对管辖区间进行了描边,没有什么内部点权值和,点个数这些信息的更新,视题目自行修改即可。
下面的例题和总结会写到。
由于 k − d k-d k−d 树是二叉搜索树结构,所以插入新的节点可以类比 B S T BST BST 的插入进行。
但随着新的节点的插入, k − d k-d k−d 树将不再平衡。
经典的解决方法是使用替罪羊树:引入一个平衡因子 α \alpha α。对于 k − d k-d k−d 树上的一个结点 x x x,在插入/删除(删除很少重构的)完以后,若其左/右子树的结点数在以 x x x 为根的子树的结点数中的占比大于 α α α,则认为以 x x x 为根的子树是不平衡的,需要重构。
重构时,先遍历子树求出一个序列(中序遍历),然后对这个序列重新建出一棵 k − d k-d k−d 树,代替原来不平衡的子树。即拍扁重构。
但是这个时效性也是不确定的,平衡因子十分的玄乎,具体细节最后总结会写到。
先阅读下方代码,重构需要注意的四大点:
void rebuild(int &now)
这个 & \& & 细节,很容易写掉。因为要直接替代原来的编号不然就白建。
i d id id 是我将子树拍扁后的序列数组。
在重构的寻找中位数函数中,不能不写第四个元素。
不写就变成默认 i d [ u ] , i d [ v ] id[u],id[v] id[u],id[v] 的一维比较了,要比较的是 i d [ u ] , i d [ v ] id[u],id[v] id[u],id[v] 在 k − d k-d k−d 树上储存的点。
build(1, tot, t[now].dim)
:因为我的写法原因,维数是循环的,重构 n o w now now 的子树,必须从 n o w now now 之前划分的参照维度开始循环。
不能一味从 0 0 0 开始,这样后面的维度划分全都错位,丧失了二叉搜索树的有序性质。
重构是对编号的重新分配,但每个编号对应的树上点代表的实点仍不变。偏序关系仍是成立的。
int build( int l, int r, int d ) {
if( l > r ) return 0;
dim = d;
nth_element( id + l, id + mid, id + r + 1, []( int a, int b ) { return t[a].v < t[b].v; } );
int now = id[mid]; t[now].dim = d;
t[now].lson = build( l, mid - 1, d ^ 1 );
t[now].rson = build( mid + 1, r, d ^ 1 );
pushup( now );
return now;
}
void dfs( int now ) {
if( ! now ) return;
dfs( t[now].lson );
id[++ tot] = now;
dfs( t[now].rson );
}
void rebuild( int &now ) {
tot = 0;
dfs( now );
now = build( 1, tot, t[now].dim );
}
void insert( int &now, point p, int d ) {
if( ! now ) {
t[now = ++ cnt].siz = 1;
t[now].Max = t[now].Min = t[now].v = p;
t[now].dim = d;
//还需记录的信息视情况而定
return;
}
dim = d;
if( p < t[now].v ) insert( t[now].lson, p, d ^ 1 );
else insert( t[now].rson, p, d ^ 1 );
pushup( now );
if( bad( now ) ) rebuild( now );
}
由于 k-d tree \text{k-d tree} k-d tree 需要保证结构的空间划分性质,所以不能直接使用一些平衡树的删除方式。
可以在删除的节点上保留一个已删除标记,并且从这个点到根自底向上重新合并一遍,从而在它的祖先中抹除它的信息,在进行重构时再真正地删除它。
所以要记录每个实点在 k-d tree \text{k-d tree} k-d tree 上的点编号。
时间复杂度 O ( log n ) O(\log n) O(logn)。
具体代码看例题《卡常数》。
k − d k-d k−d 树的时间复杂度,除了重构,就是这个查询决定了。
估价函数就像 A ∗ A* A∗ 里的一样提前预判。整的那么高级,其实就是剪枝哈哈哈。
因为这个查询如果没有任何简直,那么跟暴力就没啥区别了。
假设要求离一个已知点 p p p 的最远点距离。
如果不设计剪枝,那么跟枚举最远点再算距离没有任何区别。
如果设计剪枝,又怎么设计呢?
我们知道树上一个点会管辖一个维度空间,那么当走到树上某个点时,这个点子树内的点一定都在管辖区间内。
我们大可在该点计算一下 p p p 到这个维度空间的最远距离。(与边框点的距离)
如果算出来的最远距离都小于当前答案就没必要往下继续查了。
(最近点距离就查最近距离)
这里距离视题目而定,欧几里得距离?曼哈顿距离?切比雪夫距离?
看似很无脑的剪枝,但是真的很有用。
如果没有返回,就有可能在这个点的子树内更新距离。
到每个点时,先算一下这个点代表的实点的距离,看能否更新。
然后算 p p p 到这个点左右儿子管辖维度空间的距离。
这里有人设计剪枝是,如果左儿子最大距离更大就先找左儿子,否则先走右儿子。
说实话这个剪枝的时效性是由数据决定的,如果边框点附近有点那么这个剪枝就是很有效的。
加上反正不会错,遇到这种数据那皆大欢喜。所以一般还是可以写这个剪枝。
具体代码见《最近最远点对》。
如果是高维空间内的信息查询问题,就是线段树常解决的,最大值?最小值?内部点个数?内部和?
剪枝设计可类比线段树。考虑查询的高维空间和树上某点的管辖空间。
完全不交,直接返回。
被完全包含,直接返回区间整体信息。
部分交,就继续往左右儿子查询。
模板可见《简单题》。
剪枝各种各样取决于题目所求。
假设两个点 ( x 1 , y 1 ) ( x 2 , y 2 ) (x_1,y_1)(x_2,y_2) (x1,y1)(x2,y2)。
曼哈顿距离是横纵坐标差之和,即 ∣ x 1 − x 2 ∣ + ∣ y 1 + y 2 ∣ |x_1-x_2|+|y_1+y_2| ∣x1−x2∣+∣y1+y2∣。
切比雪夫距离是横纵坐标差的较大值,即 max ( ∣ x 1 − x 2 ∣ , ∣ y 1 − y 2 ∣ ) \max(|x_1-x_2|,|y_1-y_2|) max(∣x1−x2∣,∣y1−y2∣)。
看似两种距离没有什么关系。不妨画个图。
考虑最简单的情况,如果用曼哈顿距离表示距离原点为 1 1 1 的所有点会构成边长 2 \sqrt{2} 2 的正方形。
如果用切比雪夫距离表示距离原点为 1 1 1 的所有点会构成边长 2 2 2 的边长。
仔细对比两个图形是有一定联系的,我们猜想有固定的方式使得两者可以相互转化。
事实证明的确如此,前人智慧!
切比雪夫距离在计算的时候需要取 max \max max,往往不是很好优化,对于一个点,计算其他点到该的距离的复杂度为 O ( n ) O(n) O(n)。
而曼哈顿距离只有求和以及取绝对值两种运算,把坐标排序后可以去掉绝对值的影响,进而用前缀和优化,可以把复杂度降为 O ( 1 ) O(1) O(1)。
将切比雪夫转化为曼哈顿距离就能将复杂度降下来。
但在 K-D tree \text{K-D tree} K-D tree 中我们反而更喜欢 max \text{max} max 因为这可以成为一个偏序关系。(绝对值打开, − a ≤ x ≤ a -a\le x\le a −a≤x≤a 就是一段区间了)
在图形上来看就是比起斜着的我们更喜欢平行坐标系的图形。
这个就是“旋转坐标系”。
这个旋转通常会在原题上看到绝对值的痕迹。
温馨提示:从《弹跳》开始往后都是更灵活的应用,有思维和码力的丢丢要求。之前的都是模板题找感觉。
后面的题目是建立在前面大量模板题的刷题形成的感觉基础上。也不太好解释,但是后面的题目会对前面感觉的纠正与强化。
每道题作者都犯了错
作者是通过《IPSC》一题加强了刻画,以及建树形成的高维空间形象。
作者是通过《崂山白花蛇草水》一题加强了多维空间建树的有序性。
每个人形成的感觉都不一样,所以后面的路只能自己走了。这个高维数据结构不像一维的二叉树可以画图直观感受。
别问为什么那么多模板题,问就是作者菜
洛谷链接
模板题,动态插点,问了再插。
#include
using namespace std;
#define maxn 100005
#define eps 1e-5
struct point { double x, y; }g[maxn];
struct node { point Max, Min, v; int lson, rson; }t[maxn << 2];
int n, dim, root, cnt;
double AnsMin = 1e18, AnsMax = -1e18;
#define lson t[now].lson
#define rson t[now].rson
#define mid (l + r >> 1)
double dis( point a, point b ) {
return sqrt( (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) );
}
bool operator < ( point a, point b ) {
if( dim == 0 ) return a.x < b.x;
if( dim == 1 ) return a.y < b.y;
}
void chkmin( point &a, point b ) {
a.x = min( a.x, b.x );
a.y = min( a.y, b.y );
}
void chkmax( point &a, point b ) {
a.x = max( a.x, b.x );
a.y = max( a.y, b.y );
}
void pushup( int now ) {
if( lson ) {
chkmin( t[now].Min, t[lson].Min );
chkmax( t[now].Max, t[lson].Max );
}
if( rson ) {
chkmin( t[now].Min, t[rson].Min );
chkmax( t[now].Max, t[rson].Max );
}
}
void insert( int &now, int l, int r, point p, int d ) {
if( ! now ) {
now = ++ cnt;
t[now].Max = t[now].Min = t[now].v = p;
return;
}
dim = d;
if( p < t[now].v ) insert( lson, l, mid, p, d ^ 1 );
else insert( rson, mid + 1, r, p, d ^ 1 );
pushup( now );
}
double DisMin( int now, point p ) {
double x, y;
if( t[now].Max.x < p.x ) x = p.x - t[now].Max.x;
else if( t[now].Min.x > p.x ) x = t[now].Min.x - p.x;
else x = 0;
if( t[now].Max.y < p.y ) y = p.y - t[now].Max.y;
else if( t[now].Min.y > p.y ) y = t[now].Min.y - p.y;
else y = 0;
return sqrt( x * x + y * y );
}
double DisMax( int now, point p ) {
double x = max( fabs( t[now].Max.x - p.x ), fabs( t[now].Min.x - p.x ) );
double y = max( fabs( t[now].Max.y - p.y ), fabs( t[now].Min.y - p.y ) );
return sqrt( x * x + y * y );
}
void QueryMin( int now, point p ) {
if( ! now or DisMin( now, p ) > AnsMin + eps ) return;
AnsMin = min( AnsMin, dis( t[now].v, p ) );
double l = DisMin( lson, p );
double r = DisMin( rson, p );
if( l < AnsMin ) QueryMin( lson, p );
if( r < AnsMin ) QueryMin( rson, p );
}
void QueryMax( int now, point p ) {
if( ! now or DisMax( now, p ) < AnsMax - eps ) return;
AnsMax = max( AnsMax, dis( t[now].v, p ) );
double l = DisMax( lson, p );
double r = DisMax( rson, p );
if( l > AnsMax ) QueryMax( lson, p );
if( r > AnsMax ) QueryMax( rson, p );
}
int main() {
scanf( "%d", &n );
for( int i = 1;i <= n;i ++ ) scanf( "%lf %lf", &g[i].x, &g[i].y );
random_shuffle( g + 1, g + n + 1 );
for( int i = 1;i <= n;i ++ ) {
QueryMin( root, g[i] );
QueryMax( root, g[i] );
insert( root, 1, n, g[i], 0 );
}
printf( "%.2f %.2f\n", AnsMin, AnsMax );
return 0;
}
洛谷链接
动态插点后,带重构,就只需要求最近的点距离了。
//[Violet]天使玩偶/SJY摆棋子
#include
using namespace std;
#define maxn 600005
struct point { int x, y; }g[maxn];
struct node { point Min, Max, v; int lson, rson, siz, dim; }t[maxn];
int n, m, cnt, root, siz, dim, tot, ans;
int id[maxn];
#define mid (l + r >> 1)
#define alpha 0.85
bool bad( int now ) {
return t[now].siz * alpha <= max( t[t[now].lson].siz, t[t[now].rson].siz );
}
bool operator < ( point a, point b ) {
if( dim == 0 ) return a.x < b.x;
if( dim == 1 ) return a.y < b.y;
}
void chkmin( point &a, point b ) {
a.x = min( a.x, b.x );
a.y = min( a.y, b.y );
}
void chkmax( point &a, point b ) {
a.x = max( a.x, b.x );
a.y = max( a.y, b.y );
}
void pushup( int now ) {
t[now].siz = 1;
int lson = t[now].lson, rson = t[now].rson;
if( lson ) {
t[now].siz += t[lson].siz;
chkmin( t[now].Min, t[lson].Min );
chkmax( t[now].Max, t[lson].Max );
}
if( rson ) {
t[now].siz += t[rson].siz;
chkmin( t[now].Min, t[rson].Min );
chkmax( t[now].Max, t[rson].Max );
}
}
int build( int l, int r, int d ) {
if( l > r ) return 0;
dim = d;
nth_element( id + l, id + mid, id + r + 1, []( int a, int b ) { return t[a].v < t[b].v; } );
int now = id[mid]; t[now].dim = d;
t[now].lson = build( l, mid - 1, d ^ 1 );
t[now].rson = build( mid + 1, r, d ^ 1 );
pushup( now );
return now;
}
void dfs( int now ) {
if( ! now ) return;
dfs( t[now].lson );
id[++ tot] = now;
dfs( t[now].rson );
}
void rebuild( int &now ) {
tot = 0;
dfs( now );
now = build( 1, tot, t[now].dim );
}
void insert( int &now, point p, int d ) {
if( ! now ) {
t[now = ++ cnt].siz = 1;
t[now].Max = t[now].Min = t[now].v = p;
t[now].dim = d;
return;
}
dim = d;
if( p < t[now].v ) insert( t[now].lson, p, d ^ 1 );
else insert( t[now].rson, p, d ^ 1 );
pushup( now );
if( bad( now ) ) rebuild( now );
}
int dis( point a, point b ) { return fabs( a.x - b.x ) + fabs( a.y - b.y ); }
int MinDis( int now, point p ) {
int x, y;
if( t[now].Max.x < p.x ) x = p.x - t[now].Max.x;
else if( p.x < t[now].Min.x ) x = t[now].Min.x - p.x;
else x = 0;
if( t[now].Max.y < p.y ) y = p.y - t[now].Max.y;
else if( p.y < t[now].Min.y ) y = t[now].Min.y - p.y;
else y = 0;
return x + y;
}
void query( int now, point p ) {
if( ! now ) return;
ans = min( ans, dis( t[now].v, p ) );
int disl = MinDis( t[now].lson, p );
int disr = MinDis( t[now].rson, p );
if( disl < disr ) {
if( disl < ans ) query( t[now].lson, p );
if( disr < ans ) query( t[now].rson, p );
}
else {
if( disr < ans ) query( t[now].rson, p );
if( disl < ans ) query( t[now].lson, p );
}
}
void build( int &now, int l, int r, int d ) {
now = ++ cnt; dim = d;
nth_element( g + l, g + mid, g + r + 1 );
t[now].Max = t[now].Min = t[now].v = g[mid]; t[now].dim = d;
if( l < mid ) build( t[now].lson, l, mid - 1, d ^ 1 );
if( mid < r ) build( t[now].rson, mid + 1, r, d ^ 1 );
pushup( now );
}
int main() {
scanf( "%d %d", &n, &m );
for( int i = 1, x, y;i <= n;i ++ ) scanf( "%d %d", &g[i].x, &g[i].y );
build( root, 1, n, 0 );
for( int i = 1, op, x, y;i <= m;i ++ ) {
scanf( "%d %d %d", &op, &x, &y );
if( op & 1 ) insert( root, (point) { x, y }, t[root].dim );
else ans = 0x7f7f7f7f, query( root, (point) { x, y } ), printf( "%d\n", ans );
}
return 0;
}
洛谷链接
K K K 没有多大,可以开一个 K K K 大小的优先队列,不停更新即可。
// [CQOI2016]K远点对
#include
using namespace std;
#define int long long
#define maxn 100005
int n, k, dim, cnt, root;
priority_queue < int, vector < int >, greater < int > > q;
struct point { int x, y; };
struct node { point Max, Min, v; int lson, rson; }t[maxn << 1];
#define lson t[now].lson
#define rson t[now].rson
#define mid (l + r >> 1)
bool operator < ( point a, point b ) {
if( dim == 0 ) return a.x < b.x;
if( dim == 1 ) return a.y < b.y;
}
void chkmin( point &a, point b ) {
a.x = min( a.x, b.x );
a.y = min( a.y, b.y );
}
void chkmax( point &a, point b ) {
a.x = max( a.x, b.x );
a.y = max( a.y, b.y );
}
void pushup( int now ) {
if( lson ) {
chkmin( t[now].Min, t[lson].Min );
chkmax( t[now].Max, t[lson].Max );
}
if( rson ) {
chkmin( t[now].Min, t[rson].Min );
chkmax( t[now].Max, t[rson].Max );
}
}
void insert( int &now, int l, int r, int d, point p ) {
if( ! now ) {
now = ++ cnt;
t[now].Max = t[now].Min = t[now].v = p;
return;
}
dim = d;
if( p < t[now].v ) insert( lson, l, mid, d ^ 1, p );
else insert( rson, mid + 1, r, d ^ 1, p );
pushup( now );
}
int dis( point a, point b ) {
return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
}
int disMax( int now, point p ) {
int x = max( fabs( t[now].Max.x - p.x ), fabs( t[now].Min.x - p.x ) );
int y = max( fabs( t[now].Max.y - p.y ), fabs( t[now].Min.y - p.y ) );
return x * x + y * y;
}
void query( int now, int dim, point p ) {
if( ! now ) return;
int len = dis( t[now].v, p );
//优先队列的大小比较方式与外面的恰恰相反
//而我又是写的重载
//所以只能重新重载一个>符号来比较
if( len > q.top() ) q.pop(), q.push( len );
int disl = disMax( lson, p );
int disr = disMax( rson, p );
len = q.top();
if( disl > disr ) {
if( disl > len ) query( lson, dim ^ 1, p );
len = q.top();
if( disr > len ) query( rson, dim ^ 1, p );
}
else {
if( disr > len ) query( rson, dim ^ 1, p );
len = q.top();
if( disl > len ) query( lson, dim ^ 1, p );
}
}
signed main() {
scanf( "%lld %lld", &n, &k );
for( int i = 1;i <= k;i ++ ) q.push( 0 );
for( int i = 1, x, y;i <= n;i ++ ) {
scanf( "%lld %lld", &x, &y );
query( root, 0, (point) { x, y } );
insert( root, 1, n, 0, (point) { x, y} );
}
printf( "%lld\n", q.top() );
return 0;
}
洛谷链接
动态插点后,略微修改一下偏序比较规则(带上编号),就是上一道题了。
//[国家集训队]JZPFAR
#include
using namespace std;
#define maxn 100005
#define int long long
struct point { int x, y, id; }g[maxn];
struct node { point Min, Max, v; int lson, rson; }t[maxn << 1];
struct Queue {
int dis, id;
bool operator < ( const Queue &t ) const {
return dis == t.dis ? id < t.id : dis > t.dis;
}
};
priority_queue < Queue > q;
int cnt, dim, root;
#define lson t[now].lson
#define rson t[now].rson
#define mid (l + r >> 1)
bool operator < ( point a, point b ) {
if( dim == 0 ) return a.x < b.x;
if( dim == 1 ) return a.y < b.y;
}
void chkmin( point &a, point b ) {
a.x = min( a.x, b.x );
a.y = min( a.y, b.y );
}
void chkmax( point &a, point b ) {
a.x = max( a.x, b.x );
a.y = max( a.y, b.y );
}
void pushup( int now ) {
if( lson ) {
chkmin( t[now].Min, t[lson].Min );
chkmax( t[now].Max, t[lson].Max );
}
if( rson ) {
chkmin( t[now].Min, t[rson].Min );
chkmax( t[now].Max, t[rson].Max );
}
}
void build( int &now, int l, int r, int d ) {
now = ++ cnt, dim = d;
nth_element( g + l, g + mid, g + r + 1 );
t[now].Min = t[now].Max = t[now].v = g[mid];
if( l < mid ) build( lson, l, mid - 1, d ^ 1 );
if( mid < r ) build( rson, mid + 1, r, d ^ 1 );
pushup( now );
}
Queue dis( point a, point b ) {
int len = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
return (Queue) { len, a.id };
}
Queue disMax( int now, point p ) {
int x = max( fabs( t[now].Max.x - p.x ), fabs( t[now].Min.x - p.x ) );
int y = max( fabs( t[now].Max.y - p.y ), fabs( t[now].Min.y - p.y ) );
return (Queue) { x * x + y * y, 0 };
}
bool operator > ( Queue a, Queue b ) { //优先队列的大小比较跟外面重载恰恰相反
return a.dis == b.dis ? a.id < b.id : a.dis > b.dis;
}
void query( int now, int d, point p ) {//这个d好像没有什么卵用
if( ! now ) return;
Queue len = dis( t[now].v, p );
if( len > q.top() ) q.pop(), q.push( len );
Queue disl = disMax( lson, p ), disr = disMax( rson, p );
len = q.top();
if( disl > disr ) {
if( disl > len ) query( lson, d ^ 1, p );
len = q.top();
if( disr > len ) query( rson, d ^ 1, p );
}
else {
if( disr > len ) query( rson, d ^ 1, p );
len = q.top();
if( disl > len ) query( lson, d ^ 1, p );
}
}
signed main() {
int n, m, x, y, k;
scanf( "%lld", &n );
for( int i = 1;i <= n;i ++ )
scanf( "%lld %lld", &g[i].x, &g[i].y ), g[i].id = i;
build( root, 1, n, 0 );
scanf( "%lld", &m );
while( m -- ) {
scanf( "%lld %lld %lld", &x, &y, &k );
while( ! q.empty() ) q.pop();
for( int j = 1;j <= k;j ++ ) q.push( { 0, n + 1 } );
query( root, 0, (point) { x, y } );
printf( "%lld\n", q.top().id );
}
return 0;
}
K K K 近点对问题。注意,nth_element()
是真的对数组进行了排序调换的。这也是代码里 dot[i] \text{dot[i]} dot[i] 的作用。
#include
using namespace std;
#define maxn 50005
struct point { int d[5], id; }g[maxn], dot[maxn];
struct node { point Min, Max, v; int lson, rson; }t[maxn];
priority_queue < pair < int, int > > q;
int cnt, root, dim, k;
int ret[15];
#define lson t[now].lson
#define rson t[now].rson
#define mid (l + r >> 1)
bool operator < ( point a, point b ) {
return a.d[dim] < b.d[dim];
}
void chkmin( point &a, point b ) {
for( int i = 0;i < k;i ++ ) a.d[i] = min( a.d[i], b.d[i] );
}
void chkmax( point &a, point b ) {
for( int i = 0;i < k;i ++ ) a.d[i] = max( a.d[i], b.d[i] );
}
void pushup( int now ) {
if( lson ) {
chkmin( t[now].Min, t[lson].Min );
chkmax( t[now].Max, t[lson].Max );
}
if( rson ) {
chkmin( t[now].Min, t[rson].Min );
chkmax( t[now].Max, t[rson].Max );
}
}
void build( int &now, int l, int r, int di ) {
if( l > r ) { now = 0; return; }
now = ++ cnt; dim = di;
nth_element( g + l, g + mid, g + r + 1 );
t[now].Min = t[now].Max = t[now].v = g[mid];
build( lson, l, mid - 1, (di + 1) % k );
build( rson, mid + 1, r, (di + 1) % k );
pushup( now );
}
int MinDis( int now, point p ) {
static int dis[5] = {};
for( int i = 0;i < k;i ++ ) {
if( t[now].Max.d[i] < p.d[i] )
dis[i] = p.d[i] - t[now].Max.d[i];
else if( p.d[i] < t[now].Min.d[i] )
dis[i] = t[now].Min.d[i] - p.d[i];
else
dis[i] = 0;
}
int ans = 0;
for( int i = 0;i < k;i ++ )
ans += dis[i] * dis[i];
return ans;
}
int CalcDis( point a, point b ) {
int ans = 0;
for( int i = 0;i < k;i ++ )
ans += ( a.d[i] - b.d[i] ) * ( a.d[i] - b.d[i] );
return ans;
}
void query( int now, point p ) {
if( ! now or MinDis( now, p ) >= q.top().first ) return;
int dis = CalcDis( t[now].v, p );
if( q.top().first > dis )
q.pop(), q.push( make_pair( dis, t[now].v.id ) );
int disl = MinDis( lson, p );
int disr = MinDis( rson, p );
if( disl < disr ) {
if( disl < q.top().first ) query( lson, p );
if( disr < q.top().first ) query( rson, p );
}
else {
if( disr < q.top().first ) query( rson, p );
if( disl < q.top().first ) query( lson, p );
}
}
int main() {
int n, T, m; point p;
while( ~ scanf( "%d %d", &n, &k ) ) {
for( int i = 1;i <= n;i ++ ) {
g[i].id = i;
for( int j = 0;j < k;j ++ )
scanf( "%d", &g[i].d[j] ), dot[i] = g[i];
}
cnt = 0;
build( root, 1, n, 0 );
scanf( "%d", &T );
while( T -- ) {
for( int i = 0;i < k;i ++ )
scanf( "%d", &p.d[i] );
scanf( "%d", &m );
while( ! q.empty() ) q.pop();
for( int i = 1;i <= m;i ++ )
q.push( make_pair( 0x3f3f3f3f, 0 ) );
query( root, p );
printf( "the closest %d points are: \n", m );
int ip = 0;
while( ! q.empty() ) ret[++ ip] = q.top().second, q.pop();
for( int i = m;i;i -- ) {
for( int j = 0;j < k;j ++ )
printf( "%d ", dot[ret[i]].d[j] );
puts("");
}
}
}
return 0;
}
洛谷链接
与二叉树同样的,一个点可以维护整个区间的信息和,查询的估价函数设计板题。
如果查询的矩形与这个树上点管辖的矩形完全不交,就直接返回;
完全包含,就直接加上内部所有点贡献。
如果部分交,就继续往下查。
//简单题
#include
using namespace std;
#define int long long
#define maxn 2000000
struct point { int x, y; };
struct node { point Max, Min, v; int lson, rson, sum, siz, val, dim; }t[maxn];
int X1, Y1, X2, Y2, K, n, opt;
int cnt, root, dim, ans, siz;
int id[maxn];
#define mid (l + r >> 1)
#define alpha 0.75
bool bad( int now ) {
return alpha * t[now].siz <= max( t[t[now].lson].siz, t[t[now].rson].siz );
}
bool operator < ( point a, point b ) {
if( dim == 0 ) return a.x < b.x;
if( dim == 1 ) return a.y < b.y;
}
bool operator == ( point a, point b ) {
return a.x == b.x and a.y == b.y;
}
bool cmp( int a, int b ) {
if( dim == 0 ) return t[a].v.x < t[b].v.x;
if( dim == 1 ) return t[a].v.y < t[b].v.y;
}
void chkmin( point &a, point b ) {
a.x = min( a.x, b.x );
a.y = min( a.y, b.y );
}
void chkmax( point &a, point b ) {
a.x = max( a.x, b.x );
a.y = max( a.y, b.y );
}
void pushup( int now ) {
t[now].sum = t[now].val, t[now].siz = 1;
int lson = t[now].lson, rson = t[now].rson;
if( lson ) {
t[now].sum += t[lson].sum;
t[now].siz += t[lson].siz;
chkmin( t[now].Min, t[lson].Min );
chkmax( t[now].Max, t[lson].Max );
}
if( rson ) {
t[now].sum += t[rson].sum;
t[now].siz += t[rson].siz;
chkmin( t[now].Min, t[rson].Min );
chkmax( t[now].Max, t[rson].Max );
}
}
int build( int l, int r, int d ) {
if( l > r ) return 0;
dim = d;
nth_element( id + l, id + mid, id + r + 1, cmp );
int now = id[mid]; t[now].dim = d;
t[now].lson = build( l, mid - 1, d ^ 1 );
t[now].rson = build( mid + 1, r, d ^ 1 );
pushup( now );
return now;
}
void dfs( int now ) {
if( ! now ) return;
dfs( t[now].lson );
id[++ siz] = now;
dfs( t[now].rson );
}
void rebuild( int &now ) {
siz = 0;
dfs( now );
now = build( 1, siz, t[now].dim );
//因为modify的写法原因 维度是交替的 这里的now重构也必须延续之前划分的参照维度 不然二叉搜索树的性质就被破坏了
}
void modify( int &now, int d, point p ) {
if( ! now ) {
t[now = ++ cnt].dim = d;
t[now].Max = t[now].Min = t[now].v = p;
t[now].sum = t[now].val = K, t[now].siz = 1;
return;
}
if( t[now].v == p ) { t[now].val += K, t[now].sum += K; return; }
dim = t[now].dim = d;
if( p < t[now].v ) modify( t[now].lson, d ^ 1, p );
else modify( t[now].rson, d ^ 1, p );
pushup( now );
if( bad( now ) ) rebuild( now );
}
void query( int now ) {
if( ! now ) return;
if( t[now].Max.x < X1 or t[now].Min.x > X2 or
t[now].Max.y < Y1 or t[now].Min.y > Y2 ) return;
if( X1 <= t[now].Min.x and t[now].Max.x <= X2 and
Y1 <= t[now].Min.y and t[now].Max.y <= Y2 ) { ans += t[now].sum; return; }
if( X1 <= t[now].v.x and t[now].v.x <= X2 and
Y1 <= t[now].v.y and t[now].v.y <= Y2 ) ans += t[now].val;
query( t[now].lson ), query( t[now].rson );
}
signed main() {
scanf( "%lld", &n );
while( scanf( "%lld", &opt ) ) {
switch( opt ) {
case 1 : {
scanf( "%lld %lld %lld", &X1, &Y1, &K );
X1 ^= ans, Y1 ^= ans, K ^= ans;
modify( root, t[root].dim, (point) { X1, Y1 } );
break;
}
case 2 : {
scanf( "%lld %lld %lld %lld", &X1, &Y1, &X2, &Y2 );
X1 ^= ans, Y1 ^= ans, X2 ^= ans, Y2 ^= ans;
ans = 0; query( root );
printf( "%lld\n", ans );
break;
}
case 3 : return 0;
}
}
return 0;
}
洛谷链接
算是对上一道题的掌握检查吧。
//巧克力王国
#include
using namespace std;
#define int long long
#define maxn 100005
struct point { int x, y, h; }g[maxn];
struct node { point Max, Min, v; int lson, rson, sum; }t[maxn];
int n, m, cnt, root, dim, ans, a, b, c;
#define lson t[now].lson
#define rson t[now].rson
#define mid (l + r >> 1)
bool operator < ( point a, point b ) {
if( dim == 0 ) return a.x < b.x;
if( dim == 1 ) return a.y < b.y;
}
void chkmin( point &a, point b ) {
a.x = min( a.x, b.x );
a.y = min( a.y, b.y );
}
void chkmax( point &a, point b ) {
a.x = max( a.x, b.x );
a.y = max( a.y, b.y );
}
void pushup( int now ) {
t[now].sum = t[now].v.h;
if( lson ) {
t[now].sum += t[lson].sum;
chkmin( t[now].Min, t[lson].Min );
chkmax( t[now].Max, t[lson].Max );
}
if( rson ) {
t[now].sum += t[rson].sum;
chkmin( t[now].Min, t[rson].Min );
chkmax( t[now].Max, t[rson].Max );
}
}
void build( int &now, int l, int r, int d ) {
now = ++ cnt; dim = d;
nth_element( g + l, g + mid, g + r + 1 );
t[now].Max = t[now].Min = t[now].v = g[mid];
if( l < mid ) build( lson, l, mid - 1, d ^ 1 );
if( mid < r ) build( rson, mid + 1, r, d ^ 1 );
pushup( now );
}
void query( int now ) {
/*
a,b,x,y可正可负
不能单单只用t[now].Max.x * a + t[now].Max.y * b来判断
*/
if( ! now ) return;
int tot = 0;
tot += t[now].Min.x * a + t[now].Min.y * b < c;
tot += t[now].Max.x * a + t[now].Max.y * b < c;
tot += t[now].Min.x * a + t[now].Max.y * b < c;
tot += t[now].Max.x * a + t[now].Min.y * b < c;
if( tot == 4 ) { ans += t[now].sum; return; }
if( tot == 0 ) return;
if( t[now].v.x * a + t[now].v.y * b < c ) ans += t[now].v.h;
query( lson ), query( rson );
}
signed main() {
scanf( "%lld %lld", &n, &m );
for( int i = 1;i <= n;i ++ )
scanf( "%lld %lld %lld", &g[i].x, &g[i].y, &g[i].h );
build( root, 1, n, 0 );
for( int i = 1;i <= m;i ++ ) {
scanf( "%lld %lld %lld", &a, &b, &c );
ans = 0; query( root );
printf( "%lld\n", ans );
}
return 0;
}
洛谷链接
这连续三道题都可以说明 K-D tree \text{K-D tree} K-D tree 是具有高维线段树,二叉搜索树的相同性质和用途的。
#include
using namespace std;
#define maxn 160005
struct point { int x, y; };
struct node { point Min, Max, v; int lson, rson, siz, sum, val, dim; }t[maxn];
int X1, Y1, X2, Y2, cnt, root, dim, tot, ans;
int id[maxn];
#define lson t[now].lson
#define rson t[now].rson
#define mid (l + r >> 1)
bool operator < ( point a, point b ) {
if( dim == 0 ) return a.x < b.x;
if( dim == 1 ) return a.y < b.y;
}
bool operator == ( point a, point b ) {
return a.x == b.x && a.y == b.y;
}
void chkmin( point &a, point b ) {
a.x = min( a.x, b.x );
a.y = min( a.y, b.y );
}
void chkmax( point &a, point b ) {
a.x = max( a.x, b.x );
a.y = max( a.y, b.y );
}
void pushup( int now ) {
t[now].siz = 1, t[now].sum = t[now].val;
if( lson ) {
t[now].siz += t[lson].siz;
t[now].sum += t[lson].sum;
chkmin( t[now].Min, t[lson].Min );
chkmax( t[now].Max, t[lson].Max );
}
if( rson ) {
t[now].siz += t[rson].siz;
t[now].sum += t[rson].sum;
chkmin( t[now].Min, t[rson].Min );
chkmax( t[now].Max, t[rson].Max );
}
}
void insert( int &now, point p, int x, int d ) {
if( ! now ) {
t[now = ++ cnt].siz = 1;
t[now].Max = t[now].Min = t[now].v = p;
t[now].val = t[now].sum = x;
t[now].dim = d;
return;
}
if( t[now].v == p ) { t[now].val += x, t[now].sum += x; return; }
dim = d;
if( t[now].v < p ) insert( lson, p, x, d ^ 1 );
else insert( rson, p, x, d ^ 1 );
pushup( now );
}
void query( int now ) {
if( ! now ) return;
if( t[now].Max.x < X1 or t[now].Min.x > X2 or
t[now].Max.y < Y1 or t[now].Min.y > Y2 ) return;
if( X1 <= t[now].Min.x and t[now].Max.x <= X2 and
Y1 <= t[now].Min.y and t[now].Max.y <= Y2 ) { ans += t[now].sum; return; }
if( X1 <= t[now].v.x and t[now].v.x <= X2 and
Y1 <= t[now].v.y and t[now].v.y <= Y2 ) ans += t[now].val;
query( lson ), query( rson );
}
int main() {
int opt, x, y, n, a;
while( scanf( "%d", &opt ) ) {
switch( opt ) {
case 0 : scanf( "%d", &n ); break;
case 1 : {
scanf( "%d %d %d", &x, &y, &a );
insert( root, (point) {x, y}, a, t[root].dim );
break;
}
case 2 : {
scanf( "%d %d %d %d", &X1, &Y1, &X2, &Y2 );
ans = 0;
query( root );
printf( "%d\n", ans );
break;
}
case 3 : return 0;
}
}
return 0;
}
洛谷链接
偏序问题的代表。
二维问题,我们通过一次排序使得一维有序,然后可以在树状数组上做查询,使得第二维也有序。
三维问题,就得再套一个 CDQ \text{CDQ} CDQ 了。
四维更是要命!我们直接好码的 k-d tree \text{k-d tree} k-d tree 就可以乱杀。
将所有点按 a a a 排序就已经使得一维有序了,动态插入,查询三维 b , c , d b,c,d b,c,d 内部的点即可。
//[CH弱省胡策R2]TATT
#include
using namespace std;
#define maxn 100005
struct point { int a, b, c, d; }g[maxn];
struct node { point Max, Min, v; int val, lson, rson, siz, dim, maxans; }t[maxn];
int tot, ans, cnt, root, dim, n;
int id[maxn], f[maxn];
#define mid (l + r >> 1)
#define alpha 0.985 //疯狂试出来的平衡因子 data3
void chkmin( point &x, point y ) {
x.a = min( x.a, y.a );
x.b = min( x.b, y.b );
x.c = min( x.c, y.c );
}
void chkmax( point &x, point y ) {
x.a = max( x.a, y.a );
x.b = max( x.b, y.b );
x.c = max( x.c, y.c );
}
bool bad( int now ) {
return alpha * t[now].siz <= max( t[t[now].lson].siz, t[t[now].rson].siz );
}
bool operator < ( point x, point y ) {
if( dim == 0 ) return x.a < y.a;
if( dim == 1 ) return x.b < y.b;
if( dim == 2 ) return x.c < y.c;
}
bool operator > ( point x, point y ) {
if( dim == 0 ) return x.a > y.a;
if( dim == 1 ) return x.b > y.b;
if( dim == 2 ) return x.c > y.c;
}
void pushup( int now ) {
t[now].siz = 1, t[now].maxans = t[now].val;
int lson = t[now].lson, rson = t[now].rson;
if( lson ) {
t[now].siz += t[lson].siz;
t[now].maxans = max( t[now].maxans, t[lson].maxans );
chkmin( t[now].Min, t[lson].Min );
chkmax( t[now].Max, t[lson].Max );
}
if( rson ) {
t[now].siz += t[rson].siz;
t[now].maxans = max( t[now].maxans, t[rson].maxans );
chkmin( t[now].Min, t[rson].Min );
chkmax( t[now].Max, t[rson].Max );
}
}
int build( int l, int r, int d ) {
if( l > r ) return 0;
dim = d;
nth_element( id + l, id + mid, id + r + 1, []( int x, int y ) { return t[x].v < t[y].v; } );
int now = id[mid]; t[now].dim = d;
t[now].lson = build( l, mid - 1, (d + 1) % 3 );
t[now].rson = build( mid + 1, r, (d + 1) % 3 );
pushup( now );
return now;
}
void dfs( int now ) {
if( ! now ) return;
dfs( t[now].lson );
id[++ tot] = now;
dfs( t[now].rson );
}
void rebuild( int &now ) {
tot = 0;
dfs( now );
now = build( 1, tot, t[now].dim );
}
void insert( int &now, point p, int d, int val ) {
if( ! now ) {
t[now = ++ cnt].siz = 1;
t[now].Min = t[now].Max = t[now].v = p;
t[now].val = t[now].maxans = val;
t[now].dim = d;
return;
}
dim = d;
//dim维度比较的时候=的情况也是放在左边 但是上边已经重载过<运算了 所以只好使用>
if( p > t[now].v ) insert( t[now].rson, p, (d + 1) % 3, val );
else insert( t[now].lson, p, (d + 1) % 3, val );
pushup( now );
if( bad( now ) ) rebuild( now );
}
void query( int now, point p ) {
if( ! now ) return;
if( t[now].maxans <= ans ) return;
if( t[now].Min.a > p.a or t[now].Min.b > p.b or t[now].Min.c > p.c ) return;
if( t[now].Max.a <= p.a and t[now].Max.b <= p.b and t[now].Max.c <= p.c ) {
ans = max( ans, t[now].maxans );
return;
}
if( t[now].v.a <= p.a and t[now].v.b <= p.b and t[now].v.c <= p.c )
ans = max( ans, t[now].val );
query( t[now].lson, p );
query( t[now].rson, p );
}
int main() {
scanf( "%d", &n );
for( int i = 1;i <= n;i ++ )
scanf( "%d %d %d %d", &g[i].a, &g[i].b, &g[i].c, &g[i].d );
sort( g + 1, g + n + 1, []( point x, point y ) {
if( x.a ^ y.a ) return x.a < y.a;
if( x.b ^ y.b ) return x.b < y.b;
if( x.c ^ y.c ) return x.c < y.c;
return x.d < y.d;
} );
for( int i = 1;i <= n;i ++ ) { //a已经有序了
ans = 0;
point p = { g[i].b, g[i].c, g[i].d };
query( root, p );
f[i] = ans + 1;
insert( root, p, t[root].dim, f[i] );
}
ans = 0;
for( int i = 1;i <= n;i ++ ) ans = max( ans, f[i] );
printf( "%d\n", ans );
return 0;
}
bzoj链接
延迟删除。
#include
using namespace std;
#define maxn 65540
#define inf 1e18
#define eps 1e-7
struct point { double x, y, z; };
struct node { point p; bool use; int id; }g[maxn];
int n, m, dim;
bool operator < ( point v1, point v2 ) {
if( dim == 0 ) return v1.x < v2.x;
if( dim == 1 ) return v1.y < v2.y;
if( dim == 2 ) return v1.z < v2.z;
}
bool operator < ( node x, node y ) { return x.p < y.p; }
void chkmin( point &v1, point v2 ) {
v1.x = min( v1.x, v2.x );
v1.y = min( v1.y, v2.y );
v1.z = min( v1.z, v2.z );
}
void chkmax( point &v1, point v2 ) {
v1.x = max( v1.x, v2.x );
v1.y = max( v1.y, v2.y );
v1.z = max( v1.z, v2.z );
}
double dis( point v1, point v2 ) {
return sqrt( (v1.x - v2.x) * (v1.x - v2.x) + (v1.y - v2.y) * (v1.y - v2.y) + (v1.z - v2.z) * (v1.z - v2.z) );
}
namespace KDtree {
int root, cnt;
struct Node { point Max, Min; node v; int lson, rson, fa; }t[maxn << 2];
int num[maxn];
#define lson t[now].lson
#define rson t[now].rson
double DisMin( int now, point d ) {
double x, y, z;
if( t[now].Max.x < d.x ) x = d.x - t[now].Max.x;
else if( d.x < t[now].Min.x ) x = t[now].Min.x - d.x;
else x = 0;
if( t[now].Max.y < d.y ) y = d.y - t[now].Max.y;
else if( d.y < t[now].Min.y ) y = t[now].Min.y - d.y;
else y = 0;
if( t[now].Max.z < d.z ) z = d.z - t[now].Max.z;
else if( d.z < t[now].Min.z ) z = t[now].Min.z - d.z;
else z = 0;
return sqrt( x * x + y * y + z * z );
}
double DisMax( int now, point d ) {
double x = max( fabs( t[now].Max.x - d.x ), fabs( t[now].Min.x - d.x ) );
double y = max( fabs( t[now].Max.y - d.y ), fabs( t[now].Min.y - d.y ) );
double z = max( fabs( t[now].Max.z - d.z ), fabs( t[now].Min.z - d.z ) );
return sqrt( x * x + y * y + z * z );
}
void pushup( int now ) {
if( t[now].v.use ) t[now].Min = t[now].Max = t[now].v.p;
else t[now].Min = { inf, inf, inf }, t[now].Max = { -inf, -inf, -inf };
if( lson ) {
chkmin( t[now].Min, t[lson].Min );
chkmax( t[now].Max, t[lson].Max );
}
if( rson ) {
chkmin( t[now].Min, t[rson].Min );
chkmax( t[now].Max, t[rson].Max );
}
}
void insert( int &now, int flag, node v ) {
if( ! now ) {
now = ++ cnt;
t[now].v = v;
num[v.id] = now;
pushup( now );
return;
}
dim = flag;
if( v < t[now].v ) insert( lson, (flag + 1) % 3, v ), t[lson].fa = now;
else insert( rson, (flag + 1) % 3, v ), t[rson].fa = now;
pushup( now );
}
void access( int now ) {
if( ! now ) return;
pushup( now );
access( t[now].fa );
}
void modify( int x, point p ) {
t[num[x]].v.use = 0;
access( num[x] );
insert( root, 0, (node) { p, 1, x } );
}
int query( int now, point p, double r ) {
if( ! now or DisMin(now, p) > r + eps or DisMax(now, p) < r - eps ) return 0;
if( t[now].v.use and fabs(dis(t[now].v.p, p) - r) <= eps ) return t[now].v.id;
int ans = query( lson, p, r );
if( ans ) return ans;
else return query( rson, p, r );
}
void build( int &now, int l, int r, int flag ) {
now = ++ cnt;
dim = flag;
int mid = l + r >> 1;
nth_element( g + l, g + mid, g + r + 1 );
t[now].v = g[mid];
num[g[mid].id] = now;
if( l < mid ) build( lson, l, mid - 1, (flag + 1) % 3 ), t[lson].fa = now;
if( mid < r ) build( rson, mid + 1, r, (flag + 1) % 3 ), t[rson].fa = now;
pushup( now );
}
void init() {
root = cnt = 0;
for( int i = 1;i <= n;i ++ ) {
double x, y, z;
scanf( "%lf %lf %lf", &x, &y, &z );
g[i].p = { x, y, z };
g[i].use = 1, g[i].id = i;
}
build( root, 1, n, 0 );
}
int query( point p, double r ) { return query( root, p, r ); }
}
namespace Code {
double a, b;
void encode() { scanf( "%lf %lf", &a, &b ); }
double f( double x ) { return a * x - b * sin( x ); }
double decode( double x, double lst, double l = -100, double r = 100 ) {
while( r - l >= 1e-9 ) {
double mid = ( l + r ) / 2;
if( f( mid * lst + 1 ) > x ) r = mid;
else l = mid;
}
return ( l + r ) / 2;
}
}
int main() {
scanf( "%d %d", &n, &m );
Code :: encode();
KDtree :: init();
double lastans = 0.1, r, x, y, z; int opt, p;
for( int i = 1;i <= m;i ++ ) {
scanf( "%d", &opt );
if( ! opt ) {
scanf( "%lf %lf %lf %lf", &r, &x, &y, &z );
p = Code :: decode( r, lastans, 1, n ) + 0.5;
x = Code :: decode( x, lastans );
y = Code :: decode( y, lastans );
z = Code :: decode( z, lastans );
KDtree :: modify( p, (point) { x, y, z } );
}
else {
scanf( "%lf %lf %lf %lf", &x, &y, &z, &r );
r = Code :: decode( r, lastans, 0, 400 );
x = Code :: decode( x, lastans );
y = Code :: decode( y, lastans );
z = Code :: decode( z, lastans );
lastans = KDtree :: query( (point) { x, y, z }, r );
printf( "%.0f\n", lastans );
}
}
return 0;
}
洛谷链接
k-d tree \text{k-d tree} k-d tree 的优化建图。
k-d tree \text{k-d tree} k-d tree 的每个节点维护着一个坐标和一个矩形的信息。
把原图上的点就叫做实点,编号范围为 [ 1 , n ] [1,n] [1,n];树上的节点叫做虚点,编号范围为 [ n + 1 , 2 n ] [n+1,2n] [n+1,2n]。
对于每一个实点,遍历装在这个位置的所有弹跳机。
假设当前弹跳机的时间为 t t t,起点为 u u u,上树查询。
对于在树上的一个节点 x x x
return
。return
。最后对于 ∀ x ∈ [ 1 , n ] ∀x∈[1,n] ∀x∈[1,n],从 x + n x+n x+n 向 x x x 建边即可。
但这样建出来直接跑,就等着空间爆死吧~~
我们思考,建边的目的是要知道从一个点出发能到达哪些点,但根据刚才的建图,我们完全可以知道从一个节点出发能到达哪些节点。
return
这完全是一样的!我们不用真的建出边,却能直接走边!
#include
using namespace std;
#define maxn 200000
#define Pair pair < int, int >
struct point { int x, y, id; }g[maxn];
struct node { point Min, Max, v; int lson, rson; }t[maxn];
struct jump { int nxt, val; point l, r; }E[maxn];
int n, m, w, h, cnt, root, dim, tot;
int dis[maxn], num[maxn], head[maxn];
priority_queue < Pair, vector < Pair >, greater < Pair > > q;
bool operator < ( point a, point b ) {
if( dim == 0 ) return a.x < b.x;
if( dim == 1 ) return a.y < b.y;
}
void chkmin( point &a, point b ) {
a.x = min( a.x, b.x );
a.y = min( a.y, b.y );
}
void chkmax( point &a, point b ) {
a.x = max( a.x, b.x );
a.y = max( a.y, b.y );
}
void pushup( int now ) {
int lson = t[now].lson, rson = t[now].rson;
if( lson ) {
chkmin( t[now].Min, t[lson].Min );
chkmax( t[now].Max, t[lson].Max );
}
if( rson ) {
chkmin( t[now].Min, t[rson].Min );
chkmax( t[now].Max, t[rson].Max );
}
}
void build( int &now, int l, int r, int d ) {
now = ++ cnt; dim = d;
int mid = l + r >> 1;
nth_element( g + l, g + mid, g + r + 1 );
t[now].Max = t[now].Min = t[now].v = g[mid];
num[g[mid].id] = now;
if( l < mid ) build( t[now].lson, l, mid - 1, d ^ 1 );
if( mid < r ) build( t[now].rson, mid + 1, r, d ^ 1 );
pushup( now );
}
void relax( int v, int w ) {
if( dis[v] > w )
q.push( make_pair( dis[v] = w, v ) );
}
void DuDuTan( int now, point l, point r, int w ) {
if( ! now ) return;
if( t[now].Max.x < l.x or t[now].Min.x > r.x or
t[now].Max.y < l.y or t[now].Min.y > r.y ) return;
if( l.x <= t[now].Min.x and t[now].Max.x <= r.x and
l.y <= t[now].Min.y and t[now].Max.y <= r.y ) {
relax( t[now].v.id + n, w );
return;
}
if( l.x <= t[now].v.x and t[now].v.x <= r.x and
l.y <= t[now].v.y and t[now].v.y <= r.y ) relax( t[now].v.id, w );
DuDuTan( t[now].lson, l, r, w ), DuDuTan( t[now].rson, l, r, w );
}
void dijkstra() {
memset( dis, 0x7f, sizeof( dis ) );
q.push( make_pair( dis[1] = 0, 1 ) );
while( ! q.empty() ) {
int u = q.top().second, d = q.top().first; q.pop();
if( dis[u] ^ d ) continue;
if( u > n ) { //KDTree上的虚点
relax( u - n, dis[u] );
if( t[num[u - n]].lson )
relax( t[t[num[u - n]].lson].v.id + n, dis[u] );
if( t[num[u - n]].rson )
relax( t[t[num[u - n]].rson].v.id + n, dis[u] );
}
else {//实点
for( int i = head[u];i;i = E[i].nxt )
DuDuTan( root, E[i].l, E[i].r, dis[u] + E[i].val );
}
}
}
void addedge( int u, int w, point l, point r ) {
E[++ tot] = { head[u], w, l, r };
head[u] = tot;
}
int main() {
scanf( "%d %d %d %d", &n, &m, &w, &h );
for( int i = 1;i <= n;i ++ )
scanf( "%d %d", &g[i].x, &g[i].y ), g[i].id = i;
build( root, 1, n, 0 );
for( int i = 1, p, ti, X1, Y1, X2, Y2;i <= m;i ++ ) {
scanf( "%d %d %d %d %d %d", &p, &ti, &X1, &X2, &Y1, &Y2 );
addedge( p, ti, (point){X1, Y1}, (point){X2,Y2} );
}
dijkstra();
for( int i = 2;i <= n;i ++ ) printf( "%d\n", dis[i] );
return 0;
}
/*
5 5 5 5
4 5
1 5
1 4
3 5
2 3
1 7122 2 4 4 5
2 9152 1 4 3 5
1 6403 1 5 1 5
3 5455 3 5 2 3
2 7402 1 3 1 4
6403
6403
6403
6403
*/
BZOJ3489
考虑怎么处理这个“只出现过一次的数“的限制条件。
首先能找到, l ≤ i ≤ r l\le i\le r l≤i≤r。这是一维。
只出现一次?那么上一次就不能出现在 [ l , r ] [l,r] [l,r] 里面,下一次也不能。
记 l s t i : a i lst_i:a_i lsti:ai 上一次出现的位置, n x t i : a i nxt_i:a_i nxti:ai 下一次出现的位置。
我们就又有两个偏序关系了, l s t i < l lst_i
相当于我们要的点 ( x , y , z ) (x,y,z) (x,y,z) 要满足 l ≤ x ≤ r ∧ y < l ∧ z > r l\le x\le r\wedge y
好了三维 K-D tree 直接上。
// A simple rmq problem
#include
using namespace std;
#define maxn 100005
struct point { int x, y, z; }g[maxn];
struct node { point Min, Max, v; int lson, rson, val, ans; }t[maxn];
int a[maxn], lst[maxn], nxt[maxn], pos[maxn];
int n, m, cnt, root, dim, L, R, ans;
#define lson t[now].lson
#define rson t[now].rson
#define mid (l + r >> 1)
bool operator < ( point a, point b ) {
if( dim == 0 ) return a.x < b.x;
if( dim == 1 ) return a.y < b.y;
if( dim == 2 ) return a.z < b.z;
}
void chkmin( point &a, point b ) {
a.x = min( a.x, b.x );
a.y = min( a.y, b.y );
a.z = min( a.z, b.z );
}
void chkmax( point &a, point b ) {
a.x = max( a.x, b.x );
a.y = max( a.y, b.y );
a.z = max( a.z, b.z );
}
void pushup( int now ) {
t[now].ans = t[now].val;
if( lson ) {
t[now].ans = max( t[now].ans, t[lson].ans );
chkmin( t[now].Min, t[lson].Min );
chkmax( t[now].Max, t[lson].Max );
}
if( rson ) {
t[now].ans = max( t[now].ans, t[rson].ans );
chkmin( t[now].Min, t[rson].Min );
chkmax( t[now].Max, t[rson].Max );
}
}
void build( int &now, int l, int r, int d ) {
now = ++ cnt; dim = d;
nth_element( g + l, g + mid, g + r + 1 );
t[now].Min = t[now].Max = t[now].v = g[mid];
t[now].val = t[now].ans = a[g[mid].x];
if( l < mid ) build( lson, l, mid - 1, (d + 1) % 3 );
if( mid < r ) build( rson, mid + 1, r, (d + 1) % 3 );
pushup( now );
}
void query( int now ) {
if( ! now ) return;
if( t[now].ans <= ans ) return;
if( t[now].Max.x < L or t[now].Min.x > R or
t[now].Min.y >= L or t[now].Max.z <= R ) return;
if( L <= t[now].Min.x and t[now].Max.x <= R and
t[now].Max.y < L and t[now].Min.z > R ) {
ans = max( ans, t[now].ans );
return;
}
if( L <= t[now].v.x and t[now].v.x <= R and
t[now].v.y < L and t[now].v.z > R ) ans = max( ans, t[now].val );
query( lson ), query( rson );
}
int main() {
scanf( "%d %d", &n, &m );
for( int i = 1;i <= n;i ++ ) scanf( "%d", &a[i] );
for( int i = 1;i <= n;i ++ ) lst[i] = pos[a[i]], pos[a[i]] = i;
for( int i = 1;i <= n;i ++ ) pos[i] = n + 1;
for( int i = n;i >= 1;i -- ) nxt[i] = pos[a[i]], pos[a[i]] = i;
for( int i = 1;i <= n;i ++ ) g[i] = { i, lst[i], nxt[i] };
build( root, 1, n, 0 );
ans = 0;
while( m -- ) {
int x, y;
scanf( "%d %d", &x, &y );
L = min( ( x + ans ) % n + 1, ( y + ans ) % n + 1 );
R = max( ( x + ans ) % n + 1, ( y + ans ) % n + 1 );
ans = 0;
query( root );
printf( "%d\n", ans );
}
return 0;
}
BZOJ4154
如果是将距离 a a a 不超过 l l l 的所有点都染色,可能会简单一点。
现在还多了个限制要求这些该被染的点得是 a a a 子树内部的点。
树上的偏序关系不难想到 dfs \text{dfs} dfs 序。将这些点在树上进行重编号,并记录管辖子树对应的一段连续重编号区间。
这个距离变成树上距离,我们可能会想用深度来做,但是同层隶属不同祖先的距离是不同的,估价函数不好设计很容易超时。而且是染色一堆点,我们肯定要用到懒标记,懒标记的复杂度也跟估价函数设计挂钩。问题就在于怎么设计估价函数,距离用什么估计是最优的???
重新读了一遍题目, a a a 子树内与 a a a 距离不超过 l l l,又没让染 a a a 的兄弟旁系亲属。
所以就是用深度来做第二维!这些染色点的深度一定都大于 a a a,且编号都在 a a a 管辖的区间编号内。
因为有懒标记的存在,不能直接从 a a a 开始,而是要从 a a a 开始往上跳父亲找到根,将一路上的标记释放掉才能继续。
染色的时候,就直接从根开始往下走,边走边释放懒标记,遇到在查询刻画出的三维空间内的点就染色/新增懒标记。
切记:原树上 u u u 是 f a fa fa 的儿子不能说明 K D T KDT KDT 上 u u u 的儿子不可能是 f a fa fa。
//[Ipsc2015]Generating Synergy
#include
using namespace std;
#define maxn 100005
#define mod 1000000007
struct point { int x, y; }g[maxn];
struct edge { int to, nxt; }E[maxn];
struct node { point Min, Max, v; int lson, rson, tag, color, fa; }t[maxn];
int root, cnt, dim;
int dfn[maxn], st[maxn], ed[maxn], id[maxn], dep[maxn], head[maxn], num[maxn];
void dfs( int now ) {
id[dfn[now] = st[now] = ++ cnt] = now;
for( int i = head[now];i;i = E[i].nxt ) {
dep[E[i].to] = dep[now] + 1;
dfs( E[i].to );
}
ed[now] = cnt;
}
#define lson t[now].lson
#define rson t[now].rson
#define mid (l + r >> 1)
bool operator < ( point a, point b ) {
if( dim == 0 ) return a.x < b.x;
if( dim == 1 ) return a.y < b.y;
}
void chkmin( point &a, point b ) {
a.x = min( a.x, b.x );
a.y = min( a.y, b.y );
}
void chkmax( point &a, point b ) {
a.x = max( a.x, b.x );
a.y = max( a.y, b.y );
}
void pushup( int now ) {
if( lson ) {
chkmin( t[now].Min, t[lson].Min );
chkmax( t[now].Max, t[lson].Max );
}
if( rson ) {
chkmin( t[now].Min, t[rson].Min );
chkmax( t[now].Max, t[rson].Max );
}
}
void build( int &now, int l, int r, int d ) {
if( l > r ) { now = 0; return; }
now = ++ cnt; dim = d;
nth_element( g + l, g + mid, g + r + 1 );
t[now].Min = t[now].Max = t[now].v = g[mid];
t[now].color = 1, t[now].tag = 0;
num[t[now].v.x] = now;
build( lson, l, mid - 1, d ^ 1 );
if( lson ) t[lson].fa = now;
build( rson, mid + 1, r, d ^ 1 );
if( rson ) t[rson].fa = now;
pushup( now );
}
void pushdown( int now ) {
if( ! t[now].tag ) return;
if( lson ) t[lson].tag = t[lson].color = t[now].tag;
if( rson ) t[rson].tag = t[rson].color = t[now].tag;
t[now].tag = 0;
}
void climb( int now ) {
if( ! now ) return;
climb( t[now].fa );
pushdown( now );
}
void modify( int now, int a, int l, int c ) {
if( ! now ) return;
if( t[now].Min.y > dep[a] + l or t[now].Max.x < st[a] or t[now].Min.x > ed[a] )
return;
if( st[a] <= t[now].Min.x and t[now].Max.x <= ed[a] and t[now].Max.y <= dep[a] + l ) {
t[now].tag = t[now].color = c;
return;
}
pushdown( now );
if( st[a] <= t[now].v.x and t[now].v.x <= ed[a] and t[now].v.y <= dep[a] + l )
t[now].color = c;
modify( lson, a, l, c );
modify( rson, a, l, c );
}
int main() {
int T, n, c, q, l, a;
scanf( "%d", &T );
while( T -- ) {
scanf( "%d %d %d", &n, &c, &q );
int tot = 0;
memset( head, 0, sizeof( head ) );
for( int i = 2, fa;i <= n;i ++ ) {
scanf( "%d", &fa );
E[++ tot] = (edge) {i, head[fa]};
head[fa] = tot;
}
cnt = 0;
dfs( 1 );
cnt = 0;
for( int i = 1;i <= n;i ++ ) g[i] = { dfn[i], dep[i] };
build( root, 1, n, 0 );
long long ans = 0;
for( int i = 1;i <= q;i ++ ) {
scanf( "%d %d %d", &a, &l, &c );
if( ! c ) {
climb( num[dfn[a]] );
( ans += 1ll * i * t[num[dfn[a]]].color ) %= mod;
}
else modify( root, a, l, c );
}
printf( "%lld\n", ans );
}
return 0;
}
BZOJ4605
带插入、修改的二维区间 k k k 大值在线查询。
先考虑如果就只是问所有点中的 K K K 大值,(离散)权值线段树可做,各种(平衡树)也可以做。
考虑二叉搜索树是满足左边的权值都不大于自己,右边权值都不小于自己。
然后用 siz 来锐减 K,只用 O(log )的时间。
这里我们直接类比,K 大值就是找 K 个,查询同样用 siz 来做。
在刻画出来的二维图形内进行类似平衡树的操作,左右子树 siz 判断走左子树还是右子树,继续搜还是减子树个数直接返回劈里啪啦。
第 K 大先走右儿子再走左儿子。
以上都是错的——因为 K D T KDT KDT 只保证当前划分参照维度是有序的。(说到底该层也只是一维平衡树)
直接在二分第 K K K 大的值,然后在 K D T KDT KDT 上查刻画空间内值比二分值大的个数有多少个,调整即可。
线段树套KDT的是觉得二分短小精悍作用相同不香吗?
#include
using namespace std;
#define maxn 600005
struct point { int x, y; };
struct node { point Min, Max, v;int lson, rson, siz, val, dim, minv, maxv; }t[maxn];
int cnt, root, dim, tot, X1, Y1, X2, Y2, ans, k;
int id[maxn];
const double alpha = 0.825;
bool bad( int now ) {
return alpha * t[now].siz <= max( t[t[now].lson].siz, t[t[now].rson].siz );
}
bool operator < ( point a, point b ) {
if( dim == 0 ) return a.x < b.x;
if( dim == 1 ) return a.y < b.y;
}
void chkmin( point &a, point b ) {
a.x = min( a.x, b.x );
a.y = min( a.y, b.y );
}
void chkmax( point &a, point b ) {
a.x = max( a.x, b.x );
a.y = max( a.y, b.y );
}
void pushup( int now ) {
t[now].siz = 1;
t[now].maxv = t[now].minv = t[now].val;
int lson = t[now].lson, rson = t[now].rson;
if( lson ) {
t[now].siz += t[lson].siz;
t[now].minv = min( t[now].minv, t[lson].minv );
t[now].maxv = max( t[now].maxv, t[lson].maxv );
chkmin( t[now].Min, t[lson].Min );
chkmax( t[now].Max, t[lson].Max );
}
if( rson ) {
t[now].siz += t[rson].siz;
t[now].minv = min( t[now].minv, t[rson].minv );
t[now].maxv = max( t[now].maxv, t[rson].maxv );
chkmin( t[now].Min, t[rson].Min );
chkmax( t[now].Max, t[rson].Max );
}
}
int build( int l, int r, int d ) {
if( l > r ) return 0;
dim = d; int mid = l + r >> 1;
nth_element( id + l, id + mid, id + r + 1, []( int x, int y ) { return t[x].v < t[y].v; } );
int now = id[mid]; t[now].dim = d;
t[now].lson = build( l, mid - 1, d ^ 1 );
t[now].rson = build( mid + 1, r, d ^ 1 );
pushup( now );
return now;
}
void dfs( int now ) {
if( ! now ) return;
dfs( t[now].lson );
id[++ tot] = now;
dfs( t[now].rson );
}
void rebuild( int &now ) {
tot = 0;
dfs( now );
now = build( 1, tot, t[now].dim );
}
void insert( int &now, point p, int x, int d ) {
if( ! now ) {
now = ++ cnt;
t[now].siz = 1, t[now].dim = d;
t[now].val = t[now].maxv = t[now].minv = x;
t[now].Min = t[now].Max = t[now].v = p;
return;
}
dim = d;
if( p < t[now].v ) insert( t[now].lson, p, x, d ^ 1 );
else insert( t[now].rson, p, x, d ^ 1 );
pushup( now );
if( bad( now ) ) rebuild( now );
}
void query( int now, int val ) {
if( tot >= k ) return;
if( ! now or t[now].maxv < val ) return;
if( t[now].Max.x < X1 or t[now].Min.x > X2 or
t[now].Max.y < Y1 or t[now].Min.y > Y2 ) return;
if( X1 <= t[now].Min.x and t[now].Max.x <= X2 and
Y1 <= t[now].Min.y and t[now].Max.y <= Y2 and t[now].minv >= val ) {
tot += t[now].siz;
return;
}
if( X1 <= t[now].v.x and t[now].v.x <= X2 and
Y1 <= t[now].v.y and t[now].v.y <= Y2 and
t[now].val >= val ) tot ++;
query( t[now].lson, val ), query( t[now].rson, val );
}
void solve() {
int l = 1, r = 1e9; ans = -1;
while( l <= r ) {
int mid = l + r >> 1;
tot = 0, query( root, mid );
if( tot >= k ) ans = mid, l = mid + 1;
else r = mid - 1;
}
if( ~ ans ) printf( "%d\n", ans );
else ans = 0, puts("NAIVE!ORZzyz.");
}
int main() {
int n, Q, op, x, y, v;
scanf( "%d %d", &n, &Q );
while( Q -- ) {
scanf( "%d", &op );
if( op & 1 ) {
scanf( "%d %d %d", &x, &y, &v );
x ^= ans, y ^= ans, v ^= ans;
insert( root, (point){x, y}, v, t[root].dim );
}
else {
scanf( "%d %d %d %d %d", &X1, &Y1, &X2, &Y2, &k );
X1 ^= ans, Y1 ^= ans, X2 ^= ans, Y2 ^= ans, k ^= ans;
solve();
}
}
return 0;
}
BZOJ2898
啊看 ∣ x − y ∣ + ∣ a x − a y ∣ |x-y|+|a_x-a_y| ∣x−y∣+∣ax−ay∣ ,绝对值欸!直接旋转坐标系 ( i , a i ) → ( i + a i , i − a i ) (i,a_i)\rightarrow (i+a_i,i-a_i) (i,ai)→(i+ai,i−ai)。
询问就变成了点 ( x + a x , x − a x ) (x+a_x,x-a_x) (x+ax,x−ax) 与点 ( i + a i , i − a i ) (i+a_i,i-a_i) (i+ai,i−ai) 的切比雪夫距离 ≤ k \le k ≤k 的 i i i 的数量。
即在新坐标系上的点 ( x , y ) (x,y) (x,y) 到指定点 ( x 0 , y 0 ) (x_0,y_0) (x0,y0) 需要满足 ∣ x − x 0 ∣ ≤ k ∧ ∣ y − y 0 ∣ ≤ k |x-x_0|\le k\wedge|y-y_0|\le k ∣x−x0∣≤k∧∣y−y0∣≤k。
直接拆开 − k + x 0 ≤ x ≤ k + x 0 ∧ − k + y 0 ≤ y ≤ k + y 0 -k+x_0\le x\le k+x_0\wedge -k+y_0\le y\le k+y_0 −k+x0≤x≤k+x0∧−k+y0≤y≤k+y0,这不就是个矩形了吗?
这不就转化成了矩形内计数问题了?
又要考虑历史版本,直接把时间也弄成一维偏序。
问题就变成三维 K D T KDT KDT 计数问题了。一下子简单了好多。
当然可以在线做,就让时间自然有序,写个动态插入重构也行。
#include
using namespace std;
#define maxn 100005
struct point { int x, y, z; }g[maxn];
struct Ask { point p; int k; }Query[maxn];
struct node{ point Min, Max, v; int lson, rson, siz; }t[maxn];
int cnt, root, dim, X1, X2, Y1, Y2 , T, ans;
int a[maxn];
#define lson t[now].lson
#define rson t[now].rson
#define mid (l + r >> 1)
bool operator < ( point a, point b ) {
if( dim == 0 ) return a.x < b.x;
if( dim == 1 ) return a.y < b.y;
if( dim == 2 ) return a.z < b.z;
}
void chkmin( point &a, point b ) {
a.x = min( a.x, b.x );
a.y = min( a.y, b.y );
a.z = min( a.z, b.z );
}
void chkmax( point &a, point b ) {
a.x = max( a.x, b.x );
a.y = max( a.y, b.y );
a.z = max( a.z, b.z );
}
void pushup( int now ) {
t[now].siz = 1;
if( lson ) {
t[now].siz += t[lson].siz;
chkmin( t[now].Min, t[lson].Min );
chkmax( t[now].Max, t[lson].Max );
}
if( rson ) {
t[now].siz += t[rson].siz;
chkmin( t[now].Min, t[rson].Min );
chkmax( t[now].Max, t[rson].Max );
}
}
void build( int &now, int l, int r, int d ) {
now = ++ cnt; dim = d;
nth_element( g + l, g + mid, g + r + 1 );
t[now].Min = t[now].Max = t[now].v = g[mid];
if( l < mid ) build( lson, l, mid - 1, (d + 1) % 3 );
if( mid < r ) build( rson, mid + 1, r, (d + 1) % 3 );
pushup( now );
}
void query( int now ) {
if( ! now ) return;
if( t[now].Max.x < X1 or t[now].Min.x > X2 or
t[now].Max.y < Y1 or t[now].Min.y > Y2 or
t[now].Min.z > T ) return;
if( X1 <= t[now].Min.x and t[now].Max.x <= X2 and
Y1 <= t[now].Min.y and t[now].Max.y <= Y2 and
t[now].Max.z <= T ) { ans += t[now].siz; return; }
if( X1 <= t[now].v.x and t[now].v.x <= X2 and
Y1 <= t[now].v.y and t[now].v.y <= Y2 and
t[now].v.z <= T ) ans ++;
query( lson ), query( rson );
}
int main() {
int n, q;
scanf( "%d %d", &n, &q );
for( int i = 1, x;i <= n;i ++ ) {
scanf( "%d", &a[i] );
g[i] = { i + a[i], i - a[i], 0 };
}
int tot = n, m = 0; char op[10];
for( int i = 1, x, k;i <= q;i ++ ) {
scanf( "%s %d %d", op, &x, &k );
if( op[0] == 'Q' )
Query[++ m] = (Ask){ (point){ x + a[x], x - a[x], i }, k };
else
g[++ tot] = (point){ x + k, x - k, i }, a[x] = k;
}
build( root, 1, tot, 0 );
for( int i = 1;i <= m;i ++ ) {
X1 = Query[i].p.x - Query[i].k;
X2 = Query[i].p.x + Query[i].k;
Y1 = Query[i].p.y - Query[i].k;
Y2 = Query[i].p.y + Query[i].k;
T = Query[i].p.z;
ans = 0;
query( root );
printf( "%d\n", ans );
}
return 0;
}
K-D tree
就是用来解决多维问题下的偏序关系的。在不考虑时间的情况下按道理是可以通用乱杀的。
这个偏序关系涉及得就多了去了,只要能找到偏序关系都行。或者说 CDQ
的题我都能硬套 K-D tree
?!
想办法硬套出一个偏序关系,多套几个越能感受到 K-D tree \text{K-D tree} K-D tree 的好,代码也几乎不变, CDQ \text{CDQ} CDQ 就越套越多了。
找出代替原问题的偏序关系,将成为写 K-D tree \text{K-D tree} K-D tree 的强力保障。
K-D tree
虽然时间非常玄乎,但是大多数时候都可以被当成和分块一样的优雅暴力而被世人(博主)传颂。
数据结构套路多多,这个就很难写挂。
所以遇到三维及以上的问题,我不会 CDQ \text{CDQ} CDQ 不如直接 K-D tree \text{K-D tree} K-D tree 硬刚。
不求全过,但是拿大部分分也是很优秀的了。
比基础暴力分高的都是好做法——by博主。
对于 K-D tree \text{K-D tree} K-D tree 的理解,可以想象成平衡树套平衡树套平衡树无限嵌套,那么对于一些应用就可以仿照这些数据结构的写法。
K D T KDT KDT 的划分维度仅仅保证了区间的那一维上是有序的,其余维度是无序的。
换言之,每到新的一层 [ l , r ] [l,r] [l,r] 相当于按照 d d d 维为偏序构造了个平衡树,其余维度的偏序是不被保证的。
所以要么里面嵌套权值线段树《崂山白花蛇水草》等各种,要么扩展一维新的偏序关系。
常见的使用:
套替罪羊树,设计平衡因子,排扁重构。
平衡因子是比较玄学的一个部分。
一般而言,可以先敲一个不重构的版本,过了就不管了,没过再来加重构。
平衡因子 (作者喜欢的范围:0.75~0.985
) 决定于数据。
如果 n n n 比较小一般不用重构都能过,可以以 1 e 5 1e5 1e5 做参考。
要知道平衡因子越小(越接近 0.5 0.5 0.5 要求绝对平衡)重构的次数就越多,时间消耗也会越大。
所以一般会让其适当的倾斜。
涉及动态插入和删除。
删除并不是及时删除,而是打上标记,延后删除,利用向上合并不要这个点的信息来做到抹除这个点。
这个向上合并是要从该点开始往上一路的祖先都要重新维护新信息。
动态一个一个插入时效性不及整个序列一次性建树优秀。(尤其是还带了重构的动态插入)
估价函数(即在查询中的剪枝设计)
一般设计分为三个部分,完全无关,全部包含,仅交一部分。
前两者就可以直接在该点得到所有信息,返回;最后一个要继续往下递归下去。
每次还要考虑当前点的贡献。
查询一定是从 K-D tree \text{K-D tree} K-D tree 的树根开始往下查,根据询问刻画出的多维空间来判断每个点具体的贡献。
不能直接跳到 K-D tree \text{K-D tree} K-D tree 上某些点直接往下做。
《IPSC》这道题就不能直接从 a a a 在 K-D tree 上对应的点开始往下做。
谁能保证 a a a 原树的子树内部的点在 K D T KDT KDT 上一定也是对应点子树内的呢?
优化建图。
同线段树优化建图一样。一段连续区间用一个点来表示,就不需要太多的废边。
线段树是一维的,只能是一段连续区间。 K D T KDT KDT 就是多维的,一个矩阵就可被视为二维连续区间。
理解可以想成高维线段树。
大多数题目的差别其实就是体现在维度的个数,估价函数随题目要求不同而设计的不同,根据题目要求设定的偏序关系。
当你毫无思路的时候,不妨想想随机化偏分,网络流和k-d tree——by作者。