📅  最后修改于: 2023-12-03 15:42:22.741000             🧑  作者: Mango
在一个平面内给你 $n\ (n \leqslant 10^6)$ 条线段,支持以下操作:
这道题可以用扫描线+线段树/平衡树解决。
对于每个询问点 $(x, y)$,我们可以先把所有和 $(x, y)$ 有关的线段提取出来,这个可以用线段树/平衡树实现。
然后再将这些线段按照和点 $(x, y)$ 的距离从小到大排序,距离相同的按照斜率(也就是 $\dfrac{y_i - y}{x_i - x}$)排序。
最后直接取距离最小的线段即可。
Add 操作只需要在线段树/平衡树上插入新的线段即可。
具体实现可参考以下代码:
// C++代码
struct Line {
int id;
double k, b, l, r;
bool operator < (const Line& other) const {
if (fabs(k - other.k) < eps) {
return (k > 0 ? b > other.b : b < other.b);
} else {
return (k > other.k);
}
}
};
struct Query {
int x, y, id;
bool operator < (const Query& other) const {
return x < other.x;
}
};
vector<Query> qs;
int n, m, idx;
Line lines[MAXN << 2], tmp[MAXN << 2];
double ans[MAXN];
void divide (int l, int r) { // 线段树区间处理
if (l == r) return;
int mid = (l + r) >> 1;
divide(l, mid);
divide(mid + 1, r);
int i = l, j = mid + 1;
merge(lines + l, lines + mid + 1, lines + mid + 1, lines + r + 1, tmp);
for (int k = l; k <= r; ++k) {
if (j > r || (i <= mid && tmp[i].b >= tmp[j].b)) {
lines[k] = tmp[i];
i++;
} else {
lines[k] = tmp[j];
j++;
}
}
for (int k = l; k <= r; ++k) {
if (k <= mid) {
for (int p = j; p <= r; ++p) {
if (lines[p].k > lines[k].k) {
ans[lines[k].id] = min(ans[lines[k].id], calc_dist(lines[k], lines[p]));
break;
}
}
} else {
for (int p = i; p <= mid; ++p) {
if (lines[p].k < lines[k].k) {
ans[lines[k].id] = min(ans[lines[k].id], calc_dist(lines[k], lines[p]));
break;
}
}
}
}
}
int main () {
// 输入略去
for (int i = 1; i <= m; ++i) {
scanf("%d%d", &qs[i].x, &qs[i].y);
qs[i].id = i;
}
sort(qs + 1, qs + 1 + m);
memset(ans, 0x7f, sizeof(ans));
idx = 0;
for (int i = 1; i <= m; ++i) {
while (idx < n && lines[idx].l <= qs[i].x) idx++; // 将和询问点有关的所有线段插入
for (int j = idx - 1; j >= 0 && lines[j].r >= qs[i].x; --j) { // 从右往左扫描,因为右边的区间更小
if (calc_dist(lines[j], qs[i]) < ans[lines[j].id]) {
ans[lines[j].id] = calc_dist(lines[j], qs[i]);
}
}
Query& q = qs[i];
lines[idx++] = { i, INF, q.y + 1.0 / INF, q.x, q.x }; // INF是float最大值,保证y比所有线段的最大端点y值都大
}
divide(0, idx - 1);
for (int i = 1; i <= m; ++i) {
printf("%.2lf\n", ans[i]); // 输出答案
}
return 0;
}
对于 Add 操作,我们需要在线段树/平衡树上插入一条线段,时间复杂度 $O(\log n)$。
对于每个询问点 $(x, y)$,我们需要在线段树/平衡树上查询附近的所有线段,并将其排序,时间复杂度 $O(\log^2 n)$。
因此总时间复杂度为 $O(n\log^2 n)$。