文章目录
- K-D tree
- 建树
- 合并
- 插入
- 删除
- 查询(估价函数)
- 旋转坐标系
- 题目练习
- [SDOI2012]最近最远点对
- [Violet]天使玩偶/SJY摆棋子
- [CQOI2016]K远点对
- [国家集训队]JZPFAR
- The closest M points
- 简单题
- 巧克力王国
- [BOI2007]Mokia 摩基亚
- [CH弱省胡策R2]TATT
- [BZOJ3815]卡常数
- [NOI2019]弹跳
- A simple rmq problem
- [Ipsc2015]Generating Synergy
- 崂山白花蛇草水
- 数列
- K-D tree 总结
K-D tree
K-D tree\text{K-D tree}K-D tree 是一种对 kkk 维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。
主要应用于多维空间关键数据的搜索。
是二叉搜索树结构。
所以可以用来解决高维偏序问题,可以代替树套树以及 CDQ\text{CDQ}CDQ 分治(一般不容易写挂)。
建树
k−dk-dk−d 树的构造是基于对 KKK 维空间的分割,每次选取其中一维坐标的中位数作为划分界线。
一般循环地以每维坐标为划分依据,把中位数所在点作为该子树的根, 动态开点。
以二维为例(可以展示图片):
时间复杂度 T(n)=O(n)+T(n2)=O(nlogn)T(n)=O(n)+T(\frac n2)=O(n\log n)T(n)=O(n)+T(2n)=O(nlogn),深度为 O(logn)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)
:将 ggg 数组的 l∼rl\sim rl∼r 区间按照 cmpcmpcmp 方式比较大小,求出第 midmidmid 大的点信息。
这个函数只能保证 midmidmid 左边的都不大于 midmidmid,midmidmid 右边的都不小于 midmidmid,但不保证其内部仍然有序,即只排序了 midmidmid 这一个点。
我习惯重载结构体的大小符号比较,见上,所以就不用写后面的 cmpcmpcmp。
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 );
}
由此也可以建出 kkk 维的 k−dk-dk−d 树了。无非是维度循环的更改罢了。(d+1)%k
建树过程中根据题目要求进行信息选择记忆。最后的总结会写到。
合并
与所有二叉搜索树一样,k−dk-dk−d 树也要进行自底向上的信息合并问题。
以二维为例(比较直观能感受)
蓝色是 k−dk-dk−d 树上的自编号,旁边黑色编号表示这个点存储的点的编号。
以根 111 为例,储存的是编号为 333 的黑点信息,通过下面给出的合并操作,你会发现其实在 111 点维护的是一个(Min.x,Min.y)-(Max.x,Max.y)
的矩形。
而这个 Min.x/Min.yMin.x/Min.yMin.x/Min.y 是由黑点 111 提供的,Max.xMax.xMax.x 是由黑点 666 提供的,Max.yMax.yMax.y 是由黑点 222 提供的,这些信息都是通过合并往上传递的。
在这里再阐述 k−dk-dk−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 );}
}
kkk 维无法想象出来,但是可以类比是维护了一个 kkk 维图形。
为了方便码,可以用将维度放到一个数组里面,循环维护。
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−dk-dk−d 树是二叉搜索树结构,所以插入新的节点可以类比 BSTBSTBST 的插入进行。
但随着新的节点的插入,k−dk-dk−d 树将不再平衡。
经典的解决方法是使用替罪羊树:引入一个平衡因子 α\alphaα。对于 k−dk-dk−d 树上的一个结点 xxx,在插入/删除(删除很少重构的)完以后,若其左/右子树的结点数在以 xxx 为根的子树的结点数中的占比大于 ααα,则认为以 xxx 为根的子树是不平衡的,需要重构。
重构时,先遍历子树求出一个序列(中序遍历),然后对这个序列重新建出一棵 k−dk-dk−d 树,代替原来不平衡的子树。即拍扁重构。
但是这个时效性也是不确定的,平衡因子十分的玄乎,具体细节最后总结会写到。
先阅读下方代码,重构需要注意的四大点:
-
void rebuild(int &now)
这个 &\&& 细节,很容易写掉。因为要直接替代原来的编号不然就白建。 -
ididid 是我将子树拍扁后的序列数组。
在重构的寻找中位数函数中,不能不写第四个元素。
不写就变成默认 id[u],id[v]id[u],id[v]id[u],id[v] 的一维比较了,要比较的是 id[u],id[v]id[u],id[v]id[u],id[v] 在 k−dk-dk−d 树上储存的点。
-
build(1, tot, t[now].dim)
:因为我的写法原因,维数是循环的,重构 nownownow 的子树,必须从 nownownow 之前划分的参照维度开始循环。不能一味从 000 开始,这样后面的维度划分全都错位,丧失了二叉搜索树的有序性质。
-
重构是对编号的重新分配,但每个编号对应的树上点代表的实点仍不变。偏序关系仍是成立的。
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(logn)O(\log n)O(logn)。
具体代码看例题《卡常数》。
查询(估价函数)
k−dk-dk−d 树的时间复杂度,除了重构,就是这个查询决定了。
估价函数就像 A∗A*A∗ 里的一样提前预判。整的那么高级,其实就是剪枝哈哈哈。
因为这个查询如果没有任何简直,那么跟暴力就没啥区别了。
假设要求离一个已知点 ppp 的最远点距离。
如果不设计剪枝,那么跟枚举最远点再算距离没有任何区别。
如果设计剪枝,又怎么设计呢?
我们知道树上一个点会管辖一个维度空间,那么当走到树上某个点时,这个点子树内的点一定都在管辖区间内。
我们大可在该点计算一下 ppp 到这个维度空间的最远距离。(与边框点的距离)
如果算出来的最远距离都小于当前答案就没必要往下继续查了。
(最近点距离就查最近距离)
这里距离视题目而定,欧几里得距离?曼哈顿距离?切比雪夫距离?
看似很无脑的剪枝,但是真的很有用。
如果没有返回,就有可能在这个点的子树内更新距离。
到每个点时,先算一下这个点代表的实点的距离,看能否更新。
然后算 ppp 到这个点左右儿子管辖维度空间的距离。
这里有人设计剪枝是,如果左儿子最大距离更大就先找左儿子,否则先走右儿子。
说实话这个剪枝的时效性是由数据决定的,如果边框点附近有点那么这个剪枝就是很有效的。
加上反正不会错,遇到这种数据那皆大欢喜。所以一般还是可以写这个剪枝。
具体代码见《最近最远点对》。
如果是高维空间内的信息查询问题,就是线段树常解决的,最大值?最小值?内部点个数?内部和?
剪枝设计可类比线段树。考虑查询的高维空间和树上某点的管辖空间。
完全不交,直接返回。
被完全包含,直接返回区间整体信息。
部分交,就继续往左右儿子查询。
模板可见《简单题》。
剪枝各种各样取决于题目所求。
旋转坐标系
假设两个点 (x1,y1)(x2,y2)(x_1,y_1)(x_2,y_2)(x1,y1)(x2,y2)。
曼哈顿距离是横纵坐标差之和,即 ∣x1−x2∣+∣y1+y2∣|x_1-x_2|+|y_1+y_2|∣x1−x2∣+∣y1+y2∣。
切比雪夫距离是横纵坐标差的较大值,即 max(∣x1−x2∣,∣y1−y2∣)\max(|x_1-x_2|,|y_1-y_2|)max(∣x1−x2∣,∣y1−y2∣)。
看似两种距离没有什么关系。不妨画个图。
考虑最简单的情况,如果用曼哈顿距离表示距离原点为 111 的所有点会构成边长 2\sqrt{2}2 的正方形。
如果用切比雪夫距离表示距离原点为 111 的所有点会构成边长 222 的边长。
仔细对比两个图形是有一定联系的,我们猜想有固定的方式使得两者可以相互转化。
事实证明的确如此,前人智慧!
- 将点 (x,y)→(x+y,x−y)(x,y)\rightarrow (x+y,x-y)(x,y)→(x+y,x−y),则原坐标系中的曼哈顿距离等于新坐标系中的切比雪夫距离。
- 将点 (x,y)→(x+y2,x−y2)(x,y)\rightarrow (\frac{x+y}2,\frac{x-y}2)(x,y)→(2x+y,2x−y),则原坐标系中的切比雪夫距离等于新坐标系中的曼哈顿距离。
切比雪夫距离在计算的时候需要取 max\maxmax,往往不是很好优化,对于一个点,计算其他点到该的距离的复杂度为 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》一题加强了刻画,以及建树形成的高维空间形象。
作者是通过《崂山白花蛇草水》一题加强了多维空间建树的有序性。
每个人形成的感觉都不一样,所以后面的路只能自己走了。这个高维数据结构不像一维的二叉树可以画图直观感受。
别问为什么那么多模板题,问就是作者菜
[SDOI2012]最近最远点对
洛谷链接
模板题,动态插点,问了再插。
#include <bits/stdc++.h>
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摆棋子
洛谷链接
动态插点后,带重构,就只需要求最近的点距离了。
//[Violet]天使玩偶/SJY摆棋子
#include <bits/stdc++.h>
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.85bool 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;
}
[CQOI2016]K远点对
洛谷链接
KKK 没有多大,可以开一个 KKK 大小的优先队列,不停更新即可。
// [CQOI2016]K远点对
#include <bits/stdc++.h>
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
洛谷链接
动态插点后,略微修改一下偏序比较规则(带上编号),就是上一道题了。
//[国家集训队]JZPFAR
#include <bits/stdc++.h>
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;
}
The closest M points
KKK 近点对问题。注意,nth_element()
是真的对数组进行了排序调换的。这也是代码里 dot[i]\text{dot[i]}dot[i] 的作用。
#include <bits/stdc++.h>
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];elsedis[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 <bits/stdc++.h>
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.75bool 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 ort[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 andY1 <= 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 <bits/stdc++.h>
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;
}
[BOI2007]Mokia 摩基亚
洛谷链接
这连续三道题都可以说明 K-D tree\text{K-D tree}K-D tree 是具有高维线段树,二叉搜索树的相同性质和用途的。
#include <bits/stdc++.h>
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 andY1 <= 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;
}
[CH弱省胡策R2]TATT
洛谷链接
偏序问题的代表。
二维问题,我们通过一次排序使得一维有序,然后可以在树状数组上做查询,使得第二维也有序。
三维问题,就得再套一个 CDQ\text{CDQ}CDQ 了。
四维更是要命!我们直接好码的 k-d tree\text{k-d tree}k-d tree 就可以乱杀。
将所有点按 aaa 排序就已经使得一维有序了,动态插入,查询三维 b,c,db,c,db,c,d 内部的点即可。
//[CH弱省胡策R2]TATT
#include <bits/stdc++.h>
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 //疯狂试出来的平衡因子 data3void 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;
}
[BZOJ3815]卡常数
bzoj链接
延迟删除。
#include <bits/stdc++.h>
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].rsondouble 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;
}
[NOI2019]弹跳
洛谷链接
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,2n][n+1,2n][n+1,2n]。
对于每一个实点,遍历装在这个位置的所有弹跳机。
假设当前弹跳机的时间为 ttt,起点为 uuu,上树查询。
-
对于在树上的一个节点 xxx
- 如果 xxx管辖的矩形完全在弹跳机的目标矩形外,
return
。 - 如果 xxx 管辖的矩形完全在弹跳机的目标矩形内,从 uuu 向 xxx 建边,权值为 ttt,
return
。 - 两区间交叉情况
- 如果 xxx 维护的实点坐标在目标矩形内,从 uuu 向 x−nx−nx−n 建边,权值为 ttt。
- 递归查询两个儿子
- 如果 xxx管辖的矩形完全在弹跳机的目标矩形外,
-
最后对于 ∀x∈[1,n]∀x∈[1,n]∀x∈[1,n],从 x+nx+nx+n 向 xxx 建边即可。
但这样建出来直接跑,就等着空间爆死吧~~
我们思考,建边的目的是要知道从一个点出发能到达哪些点,但根据刚才的建图,我们完全可以知道从一个节点出发能到达哪些节点。
- 对于一个虚点 u(u∈(n,2n])u(u\in(n,2n])u(u∈(n,2n]),能到达的节点:
- 它的两个儿子对应的虚点。
- 它所对应的实点。
- 对于一个实点 u(u∈[1,n])u(u\in[1,n])u(u∈[1,n])
- 在跑最短路时,如果从队列中拿出实点,直接遍历从 uuu 被代表的树上节点编号出发的弹跳机,上树查询。
- 对于在树上的一个虚点 x(x∈(n,2n])x(x\in(n,2n])x(x∈(n,2n])
- 如果 xxx 管辖的区间完全在目标区间外,
return
- 如果 xxx 管辖的区间完全在目标区间内,松弛 xxx。
- 对于区间部分交情况:
- 如果 xxx 对应的坐标在目标区间内,松弛 x−nx−nx−n。
- 递归查询两个儿子。
- 如果 xxx 管辖的区间完全在目标区间外,
- 松弛时加上的距离即为弹跳机用时。
这完全是一样的!我们不用真的建出边,却能直接走边!
#include <bits/stdc++.h>
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 ort[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 andl.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 andl.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 46403
6403
6403
6403
*/
A simple rmq problem
BZOJ3489
考虑怎么处理这个“只出现过一次的数“的限制条件。
首先能找到,l≤i≤rl\le i\le rl≤i≤r。这是一维。
只出现一次?那么上一次就不能出现在 [l,r][l,r][l,r] 里面,下一次也不能。
记 lsti:ailst_i:a_ilsti:ai 上一次出现的位置,nxti:ainxt_i:a_inxti:ai 下一次出现的位置。
我们就又有两个偏序关系了,lsti<llst_i<llsti<l 以及 r<nxtir<nxt_ir<nxti。
相当于我们要的点 (x,y,z)(x,y,z)(x,y,z) 要满足 l≤x≤r∧y<l∧z>rl\le x\le r\wedge y<l\wedge z>rl≤x≤r∧y<l∧z>r,是一个三维空间划分。
好了三维 K-D tree 直接上。
// A simple rmq problem
#include <bits/stdc++.h>
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 ort[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;
}
[Ipsc2015]Generating Synergy
BZOJ4154
如果是将距离 aaa 不超过 lll 的所有点都染色,可能会简单一点。
现在还多了个限制要求这些该被染的点得是 aaa 子树内部的点。
树上的偏序关系不难想到 dfs\text{dfs}dfs 序。将这些点在树上进行重编号,并记录管辖子树对应的一段连续重编号区间。
这个距离变成树上距离,我们可能会想用深度来做,但是同层隶属不同祖先的距离是不同的,估价函数不好设计很容易超时。而且是染色一堆点,我们肯定要用到懒标记,懒标记的复杂度也跟估价函数设计挂钩。问题就在于怎么设计估价函数,距离用什么估计是最优的???
重新读了一遍题目,aaa 子树内与 aaa 距离不超过 lll,又没让染 aaa 的兄弟旁系亲属。
所以就是用深度来做第二维!这些染色点的深度一定都大于 aaa,且编号都在 aaa 管辖的区间编号内。
因为有懒标记的存在,不能直接从 aaa 开始,而是要从 aaa 开始往上跳父亲找到根,将一路上的标记释放掉才能继续。
染色的时候,就直接从根开始往下走,边走边释放懒标记,遇到在查询刻画出的三维空间内的点就染色/新增懒标记。
切记:原树上 uuu 是 fafafa 的儿子不能说明 KDTKDTKDT 上 uuu 的儿子不可能是 fafafa。
//[Ipsc2015]Generating Synergy
#include <bits/stdc++.h>
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
带插入、修改的二维区间 kkk 大值在线查询。
先考虑如果就只是问所有点中的 KKK 大值,(离散)权值线段树可做,各种(平衡树)也可以做。
考虑二叉搜索树是满足左边的权值都不大于自己,右边权值都不小于自己。
然后用 siz 来锐减 K,只用 O(log )的时间。
这里我们直接类比,K 大值就是找 K 个,查询同样用 siz 来做。
在刻画出来的二维图形内进行类似平衡树的操作,左右子树 siz 判断走左子树还是右子树,继续搜还是减子树个数直接返回劈里啪啦。
第 K 大先走右儿子再走左儿子。
以上都是错的——因为 KDTKDTKDT 只保证当前划分参照维度是有序的。(说到底该层也只是一维平衡树)
直接在二分第 KKK 大的值,然后在 KDTKDTKDT 上查刻画空间内值比二分值大的个数有多少个,调整即可。
线段树套KDT的是觉得二分短小精悍作用相同不香吗?
#include <bits/stdc++.h>
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∣+∣ax−ay∣|x-y|+|a_x-a_y|∣x−y∣+∣ax−ay∣ ,绝对值欸!直接旋转坐标系 (i,ai)→(i+ai,i−ai)(i,a_i)\rightarrow (i+a_i,i-a_i)(i,ai)→(i+ai,i−ai)。
询问就变成了点 (x+ax,x−ax)(x+a_x,x-a_x)(x+ax,x−ax) 与点 (i+ai,i−ai)(i+a_i,i-a_i)(i+ai,i−ai) 的切比雪夫距离 ≤k\le k≤k 的 iii 的数量。
即在新坐标系上的点 (x,y)(x,y)(x,y) 到指定点 (x0,y0)(x_0,y_0)(x0,y0) 需要满足 ∣x−x0∣≤k∧∣y−y0∣≤k|x-x_0|\le k\wedge|y-y_0|\le k∣x−x0∣≤k∧∣y−y0∣≤k。
直接拆开 −k+x0≤x≤k+x0∧−k+y0≤y≤k+y0-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,这不就是个矩形了吗?
这不就转化成了矩形内计数问题了?
又要考虑历史版本,直接把时间也弄成一维偏序。
问题就变成三维 KDTKDTKDT 计数问题了。一下子简单了好多。
当然可以在线做,就让时间自然有序,写个动态插入重构也行。
#include <bits/stdc++.h>
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 ort[now].Max.y < Y1 or t[now].Min.y > Y2 ort[now].Min.z > T ) return;if( X1 <= t[now].Min.x and t[now].Max.x <= X2 andY1 <= 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 andY1 <= t[now].v.y and t[now].v.y <= Y2 andt[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 总结
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 的理解,可以想象成平衡树套平衡树套平衡树无限嵌套,那么对于一些应用就可以仿照这些数据结构的写法。
KDTKDTKDT 的划分维度仅仅保证了区间的那一维上是有序的,其余维度是无序的。
换言之,每到新的一层 [l,r][l,r][l,r] 相当于按照 ddd 维为偏序构造了个平衡树,其余维度的偏序是不被保证的。
所以要么里面嵌套权值线段树《崂山白花蛇水草》等各种,要么扩展一维新的偏序关系。
常见的使用:
-
套替罪羊树,设计平衡因子,排扁重构。
平衡因子是比较玄学的一个部分。
一般而言,可以先敲一个不重构的版本,过了就不管了,没过再来加重构。
平衡因子 (作者喜欢的范围:
0.75~0.985
) 决定于数据。如果 nnn 比较小一般不用重构都能过,可以以 1e51e51e5 做参考。
要知道平衡因子越小(越接近 0.50.50.5 要求绝对平衡)重构的次数就越多,时间消耗也会越大。
所以一般会让其适当的倾斜。
-
涉及动态插入和删除。
删除并不是及时删除,而是打上标记,延后删除,利用向上合并不要这个点的信息来做到抹除这个点。
这个向上合并是要从该点开始往上一路的祖先都要重新维护新信息。
动态一个一个插入时效性不及整个序列一次性建树优秀。(尤其是还带了重构的动态插入)
-
估价函数(即在查询中的剪枝设计)
一般设计分为三个部分,完全无关,全部包含,仅交一部分。
前两者就可以直接在该点得到所有信息,返回;最后一个要继续往下递归下去。
每次还要考虑当前点的贡献。
查询一定是从 K-D tree\text{K-D tree}K-D tree 的树根开始往下查,根据询问刻画出的多维空间来判断每个点具体的贡献。
不能直接跳到 K-D tree\text{K-D tree}K-D tree 上某些点直接往下做。
《IPSC》这道题就不能直接从 aaa 在 K-D tree 上对应的点开始往下做。
谁能保证 aaa 原树的子树内部的点在 KDTKDTKDT 上一定也是对应点子树内的呢?
-
优化建图。
同线段树优化建图一样。一段连续区间用一个点来表示,就不需要太多的废边。
线段树是一维的,只能是一段连续区间。KDTKDTKDT 就是多维的,一个矩阵就可被视为二维连续区间。
理解可以想成高维线段树。
大多数题目的差别其实就是体现在维度的个数,估价函数随题目要求不同而设计的不同,根据题目要求设定的偏序关系。
当你毫无思路的时候,不妨想想随机化偏分,网络流和k-d tree——by作者。