时间限制:5.0s 内存限制:256.0MB
试题来源
2012中国国家集训队命题答辩
问题描述
平面上有n个点。现在有m次询问,每次给定一个点(px, py)和一个整数k,输出n个点中离(px, py)的距离第k大的点的标号。如果有两个(或多个)点距离(px, py)相同,那么认为标号较小的点距离较大。
输入格式
第一行,一个整数n,表示点的个数。
下面n行,每行两个整数x_i, y_i,表示n个点的坐标。点的标号按照输入顺序,分别为1..n。
下面一行,一个整数m,表示询问个数。
下面m行,每行三个整数px_i, py_i, k_i,表示一个询问。
下面n行,每行两个整数x_i, y_i,表示n个点的坐标。点的标号按照输入顺序,分别为1..n。
下面一行,一个整数m,表示询问个数。
下面m行,每行三个整数px_i, py_i, k_i,表示一个询问。
输出格式
m行,每行一个整数,表示相应的询问的答案。
样例输入
3
0 0
0 1
0 2
3
1 1 2
0 0 3
0 1 1
0 0
0 1
0 2
3
1 1 2
0 0 3
0 1 1
样例输出
3
1
1
1
1
数据规模和约定
50%的数据中,n个点的坐标在某范围内随机分布。
100%的数据中,n<=10^5, m<=10^4, 1<=k<=20,所有点(包括询问的点)的坐标满足绝对值<=10^9,n个点中任意两点坐标不同,m个询问的点的坐标在某范围内随机分布。
100%的数据中,n<=10^5, m<=10^4, 1<=k<=20,所有点(包括询问的点)的坐标满足绝对值<=10^9,n个点中任意两点坐标不同,m个询问的点的坐标在某范围内随机分布。
【题解】
kd-tree。
注意求的是第k“远"
在找到时候用一个队列维护当前找到的前k远的距离(因为k最大只有20,所以不用写恶心的二分检索了)。
即dl[1..k],其中dl[k]是第k远,dl[1]是最远的。
然后估价函数的判断变成
gujia[某个方向] >= dl[k]
满足则继续往那个方向找。
ma_x[2],mi_n[2]是某个子树的里面点的坐标最大可能形成的矩形的左上角和右上角坐标。
[0]是x,[1]是y;
【代码】
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> using namespace std; const int MAXN = 150000; const int MAXK = 30; struct point { long long d[2], ma_x[2], mi_n[2]; int l, r, id; }; struct ddl { long long dis; int id; }; int n, root, now, k; point t[MAXN], p[MAXN], op; long long x[MAXN], y[MAXN]; ddl dl[MAXK]; bool cmp(point a, point b) { if (a.d[now] < b.d[now]) return true; return false; } void push_up(int rt) { int l = t[rt].l, r = t[rt].r; for (int i = 0; i <= 1; i++) { if (l) { t[rt].ma_x[i] = max(t[rt].ma_x[i], t[l].ma_x[i]); t[rt].mi_n[i] = min(t[rt].mi_n[i], t[l].mi_n[i]); } if (r) { t[rt].ma_x[i] = max(t[rt].ma_x[i], t[r].ma_x[i]); t[rt].mi_n[i] = min(t[rt].mi_n[i], t[r].mi_n[i]); } } } int build(int begin, int end, int fx) { int m = (begin + end) >> 1; now = fx; nth_element(p + begin, p + m, p + end + 1, cmp);//左闭右开区间 t[m] = p[m]; for (int i = 0; i <= 1; i++) t[m].ma_x[i] = t[m].mi_n[i] = p[m].d[i]; if (begin < m) t[m].l = build(begin, m - 1, 1 - fx); if (m < end) t[m].r = build(m + 1, end, 1 - fx); push_up(m); return m; } void input_data() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%lld%lld", &x[i], &y[i]); p[i].d[0] = x[i]; p[i].d[1] = y[i]; p[i].id = i; } root = build(1, n, 0); } long long get_dis(point a, point b) { return (a.d[0] - b.d[0])*(a.d[0] - b.d[0]) + (a.d[1] - b.d[1])*(a.d[1] - b.d[1]); } long long gujia_max(int rt) { long long temp = 0; for (int i = 0; i <= 1; i++) temp += max((t[rt].ma_x[i] - op.d[i])*(t[rt].ma_x[i] - op.d[i]), (t[rt].mi_n[i] - op.d[i])*(t[rt].mi_n[i] - op.d[i])); return temp; } void query(int rt) { long long dis = get_dis(t[rt], op); int tempk = k; while (dl[tempk].dis < dis || (dl[tempk].dis == dis && t[rt].id < dl[tempk].id)) { tempk--; if (!tempk) break; } if (tempk != k) //比第k远的大。就看看它是第几远。 { for (int i = k; i >= tempk + 2; i--) dl[i] = dl[i - 1]; dl[tempk + 1].dis = dis; dl[tempk + 1].id = t[rt].id; } long long gl = -2, gr = -2; int l = t[rt].l, r = t[rt].r; if (l) gl = gujia_max(l); if (r) gr = gujia_max(r); if (gl < gr) { if (dl[k].dis <= gr) //注意估价函数的判断 query(r); if (dl[k].dis <= gl) query(l); } else { if (dl[k].dis <= gl) query(l); if (dl[k].dis <= gr) query(r); } } void output_ans() { int m; scanf("%d", &m); for (int i = 1; i <= m; i++) { memset(dl, 255, sizeof(dl)); scanf("%lld%lld%d", &op.d[0], &op.d[1], &k); query(root); printf("%d ", dl[k].id); } } int main() { //freopen("F:\rush.txt", "r", stdin); input_data(); output_ans(); return 0; }