题目描述
银河联邦有 NNN 个行星,每个行星有三维坐标 (xi,yi,zi)(x_i, y_i, z_i)(xi,yi,zi) 和所属政治派别 pi∈{0,1}p_i \in \{0, 1\}pi∈{0,1}(000 代表工业主义,111 代表生态主义)。联邦计划在每个行星上建立超广播发射机,所有发射机具有相同的覆盖半径 RRR。接收规则如下:
- 行星 AAA 的广播可以被所有与其欧几里得距离 ≤R\le R≤R 的行星接收(包括自身)。
- 设 N+(A)N^+(A)N+(A) 表示与 AAA 政治派别相同且能接收到 AAA 广播的行星数量。
- 设 N−(A)N^-(A)N−(A) 表示与 AAA 政治派别不同且能接收到 AAA 广播的行星数量。
若 N+(A)<N−(A)N^+(A) < N^-(A)N+(A)<N−(A),则称行星 AAA 为 不稳定行星。
你的任务是:
- 选择半径 R≥0R \ge 0R≥0,使得不稳定行星的数量 DDD 最大。
- 在满足 DDD 最大的前提下,选择最小的 RRR。
- 对每组数据输出最大不稳定行星数 DDD 及对应的最小 RRR(精度 10−410^{-4}10−4)。
输入格式
多组数据。每组数据第一行为整数 NNN(1≤N≤10001 \le N \le 10001≤N≤1000)。接下来 NNN 行,每行四个整数 xi,yi,zi,pix_i, y_i, z_i, p_ixi,yi,zi,pi,坐标绝对值不超过 100001000010000,保证没有两个行星坐标完全相同。
输出格式
每组数据输出两行:第一行是最大不稳定行星数 DDD,第二行是最小半径 RRR(保留四位小数)。
题目分析
基本性质
- 当 R=0R = 0R=0 时,每个行星只能收到自己的广播,因此 N+=1N^+ = 1N+=1,N−=0N^- = 0N−=0,没有行星不稳定(1<01 < 01<0 不成立),D=0D = 0D=0。
- 随着 RRR 增大,每个行星能够接收到的其他行星越来越多。当 RRR 足够大时,所有行星都能接收到所有其他行星的广播,此时 N+N^+N+ 为与该行星相同派别的总行星数,N−N^-N− 为不同派别的总行星数。如果某个行星属于少数派,它可能变得不稳定。
- N+N^+N+ 和 N−N^-N− 只在 RRR 经过某两个行星之间的距离时才会发生变化(增加一个行星)。因此,候选 RRR 只能是 000 或某对行星之间的距离。
- 最大 NNN 是 100010001000,行星对的数量约为 5×1055 \times 10^55×105,可以在 O(N2)O(N^2)O(N2) 时间内枚举并排序。
朴素算法与复杂度瓶颈
朴素的思路是:枚举所有候选 RRR,对每个 RRR 计算所有行星的 N+N^+N+ 和 N−N^-N−,统计不稳定行星数。每个 RRR 需要 O(N2)O(N^2)O(N2) 时间,候选 RRR 有 O(N2)O(N^2)O(N2) 个,总复杂度 O(N4)O(N^4)O(N4),对于 N=1000N=1000N=1000 完全不可接受。
我们需要一个能够 增量更新 的算法,使得当 RRR 逐渐增大时,用 O(1)O(1)O(1) 或 O(logN)O(\log N)O(logN) 的时间处理每个距离事件。
解题思路
核心思想:事件驱动 + 滑动窗口
我们可以将 RRR 从 000 开始逐渐增大。当 RRR 经过某两个行星 iii 和 jjj 之间的距离时,它们彼此之间刚刚进入对方的接收范围。此时:
- 对于行星 iii:如果 pi=pjp_i = p_jpi=pj,则 N+(i)N^+(i)N+(i) 增加 111;否则 N−(i)N^-(i)N−(i) 增加 111。
- 对于行星 jjj:如果 pi=pjp_i = p_jpi=pj,则 N+(j)N^+(j)N+(j) 增加 111;否则 N−(j)N^-(j)N−(j) 增加 111。
换句话说,每个行星对 (i,j)(i, j)(i,j) 产生一个 事件,事件的距离为它们的欧几里得距离。当处理到这个事件时,需要同时更新两个行星的计数。
维护不稳定行星数
我们维护两个数组:
sameCount[k]:行星 kkk 当前 N+N^+N+ 值(包括自身)。diffCount[k]:行星 kkk 当前 N−N^-N− 值。
以及一个布尔数组 isUnstable[k] 标记行星 kkk 当前是否不稳定。
全局变量 curD 记录当前 RRR 下的不稳定行星总数。
初始状态 R=0R = 0R=0,每个行星只能收到自己:
sameCount[k] = 1diffCount[k] = 0- 没有行星不稳定,
curD = 0 - 初始最优解
bestD = 0,bestR = 0。
事件排序与批量处理
由于可能存在多个行星对距离相等的情况,我们需要将这些距离相同的事件 批量处理。处理一批事件时,先应用该批次中所有事件的增量和减量,然后再判断是否有新的最优解。这样可以保证对于同一个 RRR,所有变化都已被计入。
复杂度分析
- 计算所有距离:O(N2)O(N^2)O(N2)。
- 存储所有事件:O(N2)O(N^2)O(N2) 个事件,每个事件包含距离和两个行星的索引以及类型。
- 排序事件:O(N2logN)O(N^2 \log N)O(N2logN),对于 N=1000N=1000N=1000 约为 5×105×20≈1075 \times 10^5 \times 20 \approx 10^75×105×20≈107 次比较,可以接受。
- 扫描事件:每个事件被处理一次,每次更新两个行星的状态(O(1)O(1)O(1)),总 O(N2)O(N^2)O(N2)。
- 总复杂度 O(N2logN)O(N^2 \log N)O(N2logN),满足时间限制(666 秒)。
细节注意
- 浮点数精度:判断两个距离是否相等时,需要使用容差比较,例如
fabs(a - b) < 1e-9。但在排序和扫描事件时,我们直接使用<排序,然后通过fabs判断是否属于同一批次。 - 自身计数:初始时
sameCount包含自身,这样当处理行星对事件时,更新的都是其他行星,无需特殊处理。 - 相同距离的批量处理:在扫描事件时,先用一个循环收集所有距离为
curDist的事件,然后统一更新。这样可以避免因更新顺序不同导致的不一致。 - 最优解更新时机:在每一批事件全部处理完毕后,再判断
curD是否大于bestD。这样保证对于某个距离 RRR,我们计算的是 RRR 刚好包含所有距离 ≤R\le R≤R 的行星时的状态。
代码实现
// Hypertransmission
// UVa ID: 1325
// Verdict: Accepted
// Submission Date: 2026-04-25
// UVa Run Time: 0.360s
//
// 版权所有(C)2026,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
struct Planet {
int x, y, z, p;
};
struct Event {
double dist;
int a, b; // 行星对 (a, b),a < b
int type; // 0: 派别相同,1: 派别不同
};
bool eventLess(const Event& e1, const Event& e2) {
return e1.dist < e2.dist;
}
void solve(int N, vector<Planet>& planets) {
vector<Event> events;
// 生成所有行星对事件
for (int i = 0; i < N; ++i) {
for (int j = i + 1; j < N; ++j) {
long long dx = planets[i].x - planets[j].x;
long long dy = planets[i].y - planets[j].y;
long long dz = planets[i].z - planets[j].z;
double d = sqrt(dx * dx + dy * dy + dz * dz);
if (planets[i].p == planets[j].p) {
events.push_back({d, i, j, 0}); // 相同派别
} else {
events.push_back({d, i, j, 1}); // 不同派别
}
}
}
sort(events.begin(), events.end(), eventLess);
// 初始状态 R = 0,每个行星只能收到自己
vector<int> sameCount(N, 1); // 包括自身
vector<int> diffCount(N, 0);
vector<bool> isUnstable(N, false);
int curD = 0;
for (int i = 0; i < N; ++i) {
if (sameCount[i] < diffCount[i]) {
isUnstable[i] = true;
curD++;
}
}
int bestD = curD;
double bestR = 0.0;
size_t idx = 0;
while (idx < events.size()) {
double curDist = events[idx].dist;
vector<Event> batch;
// 收集所有距离为 curDist 的事件(容差 1e-9)
while (idx < events.size() && fabs(events[idx].dist - curDist) < 1e-9) {
batch.push_back(events[idx]);
idx++;
}
// 应用这批事件,更新计数
for (const Event& e : batch) {
// 更新行星 a
bool wasUnstableA = isUnstable[e.a];
if (e.type == 0) {
sameCount[e.a]++;
} else {
diffCount[e.a]++;
}
bool nowUnstableA = (sameCount[e.a] < diffCount[e.a]);
if (wasUnstableA && !nowUnstableA) curD--;
else if (!wasUnstableA && nowUnstableA) curD++;
isUnstable[e.a] = nowUnstableA;
// 更新行星 b
bool wasUnstableB = isUnstable[e.b];
if (e.type == 0) {
sameCount[e.b]++;
} else {
diffCount[e.b]++;
}
bool nowUnstableB = (sameCount[e.b] < diffCount[e.b]);
if (wasUnstableB && !nowUnstableB) curD--;
else if (!wasUnstableB && nowUnstableB) curD++;
isUnstable[e.b] = nowUnstableB;
}
// 更新最优解
if (curD > bestD) {
bestD = curD;
bestR = curDist;
}
}
printf("%d\n%.4lf\n", bestD, bestR);
}
int main() {
int N;
while (scanf("%d", &N) == 1) {
vector<Planet> planets(N);
for (int i = 0; i < N; ++i) {
scanf("%d%d%d%d", &planets[i].x, &planets[i].y, &planets[i].z, &planets[i].p);
}
solve(N, planets);
}
return 0;
}
总结
本题的核心在于:
- 认识到候选 RRR 只能是 000 或某对行星之间的距离,从而将连续优化问题转化为离散事件处理问题。
- 使用事件驱动的方法,将 O(N4)O(N^4)O(N4) 的朴素算法优化到 O(N2logN)O(N^2 \log N)O(N2logN)。
- 通过批量处理相同距离的事件,避免了浮点数精度问题带来的顺序歧义。
- 维护全局不稳定计数,使得每次事件更新只需 O(1)O(1)O(1) 时间。
这种 事件扫描 + 增量更新 的技巧在很多几何问题和距离阈值问题中都有广泛应用,值得熟练掌握。

202

被折叠的 条评论
为什么被折叠?



