数据结构 Trick 之:KDT 求 k 近/远 点

注意,此 Trick 的时间复杂度是错的,但是貌似目前没人能卡满。

能够解决的问题

  • \(O(n \sqrt n)\) 可过。
  • 维护二维平面。
  • 每次求到一个点的 \(k\) 近或 \(k\) 远点。
  • \(k\) 很小(\(20\) 左右)

思路

二维空间想到 KDTree(TreeKevin Durant Tree)。

众所周知,动态维护 \(k\) 近或 \(k\) 远点只需要一个 \(k\) 个点的堆即可,那么我们就有了一个剪枝:当前节点维护的区间到询问点的理论极值(写个估价函数就行)都比不上当前 \(k\) 大/小值,就返回。

结束了,就是这么简单。

例题与代码

P2093 [国家集训队] JZPFAR - 洛谷

#include <bits/stdc++.h>
using namespace std;

namespace gxk {
	void main() ;
}
int main() {
	ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	gxk::main();
	return 0;
}

// ---

#define int long long
namespace gxk {
	constexpr int maxn = 100010;
	
	struct point {
		int x[2];
	} p;
	
	int n, m, x, b[maxn];
	
	struct value {
		int dis, idx;
		bool operator < (const value o) const {
			return dis > o.dis || (dis == o.dis && idx < o.idx);
		}
	} ;
	
	priority_queue <value> q;
	
	struct Kevin_DurantNode {
		point x, l, r;
		int ls, rs, idx;
	} nodes[maxn];
	int cnt;
	struct Kevin_DurantTree {
		void pushup(int u) {
			for (int k = 0; k < 2; k++) {
				nodes[u].l.x[k] = nodes[u].r.x[k] = nodes[u].x.x[k];
				if (nodes[u].ls) {
					nodes[u].l.x[k] = min(nodes[u].l.x[k], nodes[nodes[u].ls].l.x[k]);
					nodes[u].r.x[k] = max(nodes[u].r.x[k], nodes[nodes[u].ls].r.x[k]);
				}
				if (nodes[u].rs) {
					nodes[u].l.x[k] = min(nodes[u].l.x[k], nodes[nodes[u].rs].l.x[k]);
					nodes[u].r.x[k] = max(nodes[u].r.x[k], nodes[nodes[u].rs].r.x[k]);
				}
			}
			return ;
		}
		void build(int &u, int l, int r, int k) {
			int midd = ((l + r) >> 1);
			nth_element(b + l, b + midd, b + r + 1, [k](int a, int b){return nodes[a].x.x[k] < nodes[b].x.x[k];});
			u = b[midd];
//			cout << nodes[u].x.x[0] << ' ' << nodes[u].x.x[1] << '\n';
			if (l < midd) build(nodes[u].ls, l, midd - 1, k ^ 1);
			if (r > midd) build(nodes[u].rs, midd + 1, r, k ^ 1);
			pushup(u);
			return ;
		}
		int dis(int x, int y) {
			return (x - p.x[0]) * (x - p.x[0]) + (y - p.x[1]) * (y - p.x[1]);
		}
		int evaluate(int u) {
			return max({
				dis(nodes[u].l.x[0], nodes[u].l.x[1]), 
				dis(nodes[u].l.x[0], nodes[u].r.x[1]), 
				dis(nodes[u].r.x[0], nodes[u].l.x[1]), 
				dis(nodes[u].r.x[0], nodes[u].r.x[1])});
		}
		void query(int u) {
			value now = {evaluate(u), nodes[u].idx};
			if (!(now < q.top())) return ;
			now = {dis(nodes[u].x.x[0], nodes[u].x.x[1]), nodes[u].idx};
			if (now < q.top()) q.pop(), q.push(now);
			if (nodes[u].ls && nodes[u].rs) {
				value vl = {evaluate(nodes[u].ls), nodes[nodes[u].ls].idx}, vr = {evaluate(nodes[u].rs), nodes[nodes[u].rs].idx};
				if (vl < vr) {
					query(nodes[u].ls);
					query(nodes[u].rs);
				} else {
					query(nodes[u].rs);
					query(nodes[u].ls);
				}
			} else if (nodes[u].ls) {
				query(nodes[u].ls);
			} else if (nodes[u].rs) {
				query(nodes[u].rs);
			}
			return ;
		}
	} kdt;
	
	int root;
	
	void main() {
		cin >> n;
		cnt = n;
		for (int i = 1; i <= n; i++) {
			cin >> nodes[i].x.x[0] >> nodes[i].x.x[1];
			nodes[i].idx = i;
			b[i] = i;
		}
		kdt.build(root, 1, n, 0);
		cin >> m;
		while (m--) {
			cin >> p.x[0] >> p.x[1] >> x;
			while (!q.empty()) q.pop();
			while (x--) {
				q.push({-1, 0});
			}
			kdt.query(root);
			cout << q.top().idx << '\n';
		}
		return ;
	}
}
posted @ 2025-09-18 16:04  porse114514  阅读(10)  评论(0)    收藏  举报