[学习笔记] 乱世之神杀疯了 —— K-D tree

K-D tree

K-D tree \text{K-D tree} K-D tree 是一种对 k k k 维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。

主要应用于多维空间关键数据的搜索。

是二叉搜索树结构。

所以可以用来解决高维偏序问题,可以代替树套树以及 CDQ \text{CDQ} CDQ 分治(一般不容易写挂)。

建树

k − d k-d kd 树的构造是基于对 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 lr 区间按照 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 kd 树了。无非是维度循环的更改罢了。(d+1)%k

建树过程中根据题目要求进行信息选择记忆。最后的总结会写到。

合并

与所有二叉搜索树一样, k − d k-d kd 树也要进行自底向上的信息合并问题。

以二维为例(比较直观能感受)

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

在这里插入图片描述

蓝色是 k − d k-d kd 树上的自编号,旁边黑色编号表示这个点存储的点的编号。

以根 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 kd 树节点的结构体。

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 kd 树是二叉搜索树结构,所以插入新的节点可以类比 B S T BST BST 的插入进行。

但随着新的节点的插入, k − d k-d kd 树将不再平衡。

经典的解决方法是使用替罪羊树:引入一个平衡因子 α \alpha α。对于 k − d k-d kd 树上的一个结点 x x x,在插入/删除(删除很少重构的)完以后,若其左/右子树的结点数在以 x x x 为根的子树的结点数中的占比大于 α α α,则认为以 x x x 为根的子树是不平衡的,需要重构。

重构时,先遍历子树求出一个序列(中序遍历),然后对这个序列重新建出一棵 k − d k-d kd 树,代替原来不平衡的子树。即拍扁重构。

但是这个时效性也是不确定的,平衡因子十分的玄乎,具体细节最后总结会写到。

先阅读下方代码,重构需要注意的四大点:

  • 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 kd 树上储存的点。

  • 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 kd 树的时间复杂度,除了重构,就是这个查询决定了。

估价函数就像 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| x1x2+y1+y2

切比雪夫距离是横纵坐标差的较大值,即 max ⁡ ( ∣ x 1 − x 2 ∣ , ∣ y 1 − y 2 ∣ ) \max(|x_1-x_2|,|y_1-y_2|) max(x1x2,y1y2)

看似两种距离没有什么关系。不妨画个图。

考虑最简单的情况,如果用曼哈顿距离表示距离原点为 1 1 1 的所有点会构成边长 2 \sqrt{2} 2 的正方形。

在这里插入图片描述

如果用切比雪夫距离表示距离原点为 1 1 1 的所有点会构成边长 2 2 2 的边长。

在这里插入图片描述

仔细对比两个图形是有一定联系的,我们猜想有固定的方式使得两者可以相互转化。

事实证明的确如此,前人智慧!

  • 将点 ( x , y ) → ( x + y , x − y ) (x,y)\rightarrow (x+y,x-y) (x,y)(x+y,xy),则原坐标系中的曼哈顿距离等于新坐标系中的切比雪夫距离。
  • 将点 ( x , y ) → ( x + y 2 , x − y 2 ) (x,y)\rightarrow (\frac{x+y}2,\frac{x-y}2) (x,y)(2x+y,2xy),则原坐标系中的切比雪夫距离等于新坐标系中的曼哈顿距离。

切比雪夫距离在计算的时候需要取 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 axa 就是一段区间了)

在图形上来看就是比起斜着的我们更喜欢平行坐标系的图形。

这个就是“旋转坐标系”。

这个旋转通常会在原题上看到绝对值的痕迹。

题目练习

温馨提示:从《弹跳》开始往后都是更灵活的应用,有思维和码力的丢丢要求。之前的都是模板题找感觉。

后面的题目是建立在前面大量模板题的刷题形成的感觉基础上。也不太好解释,但是后面的题目会对前面感觉的纠正与强化。

每道题作者都犯了错

作者是通过《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.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;
}

[CQOI2016]K远点对

洛谷链接

K K K 没有多大,可以开一个 K K K 大小的优先队列,不停更新即可。

// [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

K K K 近点对问题。注意,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];
		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 <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.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 <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 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;
}

[CH弱省胡策R2]TATT

洛谷链接

偏序问题的代表。

二维问题,我们通过一次排序使得一维有序,然后可以在树状数组上做查询,使得第二维也有序。

三维问题,就得再套一个 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 <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 //疯狂试出来的平衡因子 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;
}

[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].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;
}	

[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 , 2 n ] [n+1,2n] [n+1,2n]

对于每一个实点,遍历装在这个位置的所有弹跳机。

假设当前弹跳机的时间为 t t t,起点为 u u u,上树查询。

  • 对于在树上的一个节点 x x x

    • 如果 x x x管辖的矩形完全在弹跳机的目标矩形外,return
    • 如果 x x x 管辖的矩形完全在弹跳机的目标矩形内,从 u u u x x x 建边,权值为 t t treturn
    • 两区间交叉情况
      • 如果 x x x 维护的实点坐标在目标矩形内,从 u u u x − n x−n xn 建边,权值为 t t t
      • 递归查询两个儿子
  • 最后对于 ∀ x ∈ [ 1 , n ] ∀x∈[1,n] x[1,n],从 x + n x+n x+n x x x 建边即可。

但这样建出来直接跑,就等着空间爆死吧~~

我们思考,建边的目的是要知道从一个点出发能到达哪些点,但根据刚才的建图,我们完全可以知道从一个节点出发能到达哪些节点。

  • 对于一个虚点 u ( u ∈ ( n , 2 n ] ) u(u\in(n,2n]) u(u(n,2n]),能到达的节点:
    • 它的两个儿子对应的虚点。
    • 它所对应的实点。
  • 对于一个实点 u ( u ∈ [ 1 , n ] ) u(u\in[1,n]) u(u[1,n])
    • 在跑最短路时,如果从队列中拿出实点,直接遍历从 u u u 被代表的树上节点编号出发的弹跳机,上树查询。
    • 对于在树上的一个虚点 x ( x ∈ ( n , 2 n ] ) x(x\in(n,2n]) x(x(n,2n])
      • 如果 x x x 管辖的区间完全在目标区间外,return
      • 如果 x x x 管辖的区间完全在目标区间内,松弛 x x x
      • 对于区间部分交情况:
        • 如果 x x x 对应的坐标在目标区间内,松弛 x − n x−n xn
        • 递归查询两个儿子。
    • 松弛时加上的距离即为弹跳机用时。

这完全是一样的!我们不用真的建出边,却能直接走边!

#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 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
*/

A simple rmq problem

BZOJ3489

考虑怎么处理这个“只出现过一次的数“的限制条件。

首先能找到, l ≤ i ≤ r l\le i\le r lir。这是一维。

只出现一次?那么上一次就不能出现在 [ 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<l lsti<l 以及 r < n x t i r<nxt_i r<nxti

相当于我们要的点 ( x , y , z ) (x,y,z) (x,y,z) 要满足 l ≤ x ≤ r ∧ y < l ∧ z > r l\le x\le r\wedge y<l\wedge z>r lxry<lz>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 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;
}

[Ipsc2015]Generating Synergy

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 <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

带插入、修改的二维区间 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 <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 ∣ + ∣ a x − a y ∣ |x-y|+|a_x-a_y| xy+axay ,绝对值欸!直接旋转坐标系 ( i , a i ) → ( i + a i , i − a i ) (i,a_i)\rightarrow (i+a_i,i-a_i) (i,ai)(i+ai,iai)

询问就变成了点 ( x + a x , x − a x ) (x+a_x,x-a_x) (x+ax,xax) 与点 ( i + a i , i − a i ) (i+a_i,i-a_i) (i+ai,iai) 的切比雪夫距离 ≤ 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 xx0kyy0k

直接拆开 − 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+x0xk+x0k+y0yk+y0,这不就是个矩形了吗?

这不就转化成了矩形内计数问题了?

又要考虑历史版本,直接把时间也弄成一维偏序。

问题就变成三维 K D T KDT KDT 计数问题了。一下子简单了好多。

当然可以在线做,就让时间自然有序,写个动态插入重构也行。

#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 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 总结

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作者。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值