题目描述
在一个城市中有 NNN 座高楼,每座高楼可以用平面上的一个点表示。任意三座高楼构成一个 Bermuda block\texttt{Bermuda block}Bermuda block(即一个三角形)。税收的计算方式为:平均每个 Bermuda block\texttt{Bermuda block}Bermuda block 内部包含的高楼数量。
更具体地说,设总共有 T=(N3)T = \binom{N}{3}T=(3N) 个三角形,设 SSS 为所有三角形内部包含的点的总数(不包含三角形的三个顶点),则答案为:
ans=ST ans = \frac{S}{T} ans=TS
输入包含多组数据,每组数据给出 NNN 个点的坐标。保证没有三点共线,也没有两点重合。NNN 的范围为 0≤N≤12000 \le N \le 12000≤N≤1200,输入以 N=0N = 0N=0 结束。
输出格式为 City k: x.xx,其中 x.xxx.xxx.xx 是四舍五入保留两位小数的浮点数。
题目分析
1. 问题转化
直接枚举所有三角形并统计内部点数的时间复杂度为 O(N4)O(N^4)O(N4)(枚举三角形 O(N3)O(N^3)O(N3) 再判断内部点 O(N)O(N)O(N)),显然不可行。我们需要更高效的方法。
注意到:
S=∑p∈P(包含点 p 在其内部的三角形数量) S = \sum_{p \in P} (\text{包含点 } p \text{ 在其内部的三角形数量}) S=p∈P∑(包含点 p 在其内部的三角形数量)
定义 F(p)F(p)F(p) 为包含点 ppp 的三角形个数,则:
S=∑pF(p) S = \sum_{p} F(p) S=p∑F(p)
因此,问题转化为:对于每个点 ppp,计算有多少个三角形(顶点来自其他 N−1N-1N−1 个点)包含 ppp 在其内部。
2. 核心几何性质
对于固定的点 ppp,考虑其他 N−1N-1N−1 个点。以 ppp 为原点,将这些点按极角排序。
关键观察:一个三角形 △qaqbqc\triangle q_a q_b q_c△qaqbqc 包含点 ppp 当且仅当三个点 qa,qb,qcq_a, q_b, q_cqa,qb,qc 不全部位于任何一个以 ppp 为顶点的半平面内。
换句话说,如果三个点全部位于某个角度范围小于 180∘180^\circ180∘ 的半平面内,那么 ppp 就在这个三角形的外部。
因此:
F(p)=(N−13)−G(p) F(p) = \binom{N-1}{3} - G(p) F(p)=(3N−1)−G(p)
其中 G(p)G(p)G(p) 表示“三个顶点都在同一个半平面内”的三角形个数(这些三角形不包含 ppp)。
3. 计算 G(p)G(p)G(p)
以 ppp 为原点,将其他点按极角排序,得到序列 a0,a1,…,am−1a_0, a_1, \dots, a_{m-1}a0,a1,…,am−1(m=N−1m = N-1m=N−1)。为了方便处理环形结构,我们将序列复制一倍接到末尾:am,am+1,…,a2m−1a_{m}, a_{m+1}, \dots, a_{2m-1}am,am+1,…,a2m−1,其中 ai+ma_{i+m}ai+m 的极角等于 ai+2πa_i + 2\piai+2π。
对于每个起点 aja_jaj(0≤j<m0 \le j < m0≤j<m),我们找到最大的 kkk(j<k<j+mj < k < j+mj<k<j+m)使得从 aja_jaj 到 aka_kak 的极角差 严格小于 180∘180^\circ180∘。设 cnt=k−j−1cnt = k - j - 1cnt=k−j−1 表示在 aja_jaj 之后、aka_kak 之前(不包含两端)的点数。
那么,以 aja_jaj 作为“最左侧”顶点,从这 cntcntcnt 个点中任意选取两个点,与 aja_jaj 一起构成的三角形,三个点都在同一个半平面内(即极角跨度小于 180∘180^\circ180∘)。
关键:每个满足三点共半平面的三角形,在极角排序中只有其“极角最小”的顶点会被计入一次。因此我们不需要像常见做法那样最后除以 333。这保证了计数的正确性。
于是:
G(p)=∑j=0m−1(cntj2) G(p) = \sum_{j=0}^{m-1} \binom{cnt_j}{2} G(p)=j=0∑m−1(2cntj)
4. 算法流程
- 如果 N<3N < 3N<3,答案为 0.000.000.00。
- 初始化 S=0S = 0S=0。
- 对于每个点 iii(作为原点 ppp):
- 构造其他 N−1N-1N−1 个点的相对向量,并按极角排序。
- 将排序后的极角数组复制一倍。
- 使用双指针扫描:对每个起点 jjj,找到最远的 kkk 使得角度差 <π< \pi<π。
- 计算 cnt=k−j−1cnt = k - j - 1cnt=k−j−1,累加 (cnt2)\binom{cnt}{2}(2cnt) 到 GGG。
- 计算 F(p)=(N−13)−GF(p) = \binom{N-1}{3} - GF(p)=(3N−1)−G,累加到 SSS。
- 答案 = S/(N3)S / \binom{N}{3}S/(3N),保留两位小数输出。
5. 复杂度分析
- 外层循环 O(N)O(N)O(N)
- 内层排序 O(NlogN)O(N \log N)O(NlogN),双指针 O(N)O(N)O(N)
- 总复杂度 O(N2logN)O(N^2 \log N)O(N2logN)
当 N=1200N = 1200N=1200 时,约 12002×10≈1.44×1071200^2 \times 10 \approx 1.44 \times 10^712002×10≈1.44×107 次操作,可以在时限内通过。
代码实现
// Strange Tax Calculation
// UVa ID: 11529
// Verdict: Accepted
// Submission Date: 2026-04-26
// UVa Run Time: 0.330s
//
// 版权所有(C)2026,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
struct Point {
long long x, y;
Point() {}
Point(long long _x, long long _y) : x(_x), y(_y) {}
};
Point operator - (const Point& a, const Point& b) {
return Point(a.x - b.x, a.y - b.y);
}
long long cross(const Point& a, const Point& b) {
return a.x * b.y - a.y * b.x;
}
// 判断点所在的象限,用于极角排序
int quad(const Point& p) {
if (p.x > 0 && p.y >= 0) return 1;
if (p.x <= 0 && p.y > 0) return 2;
if (p.x < 0 && p.y <= 0) return 3;
return 4;
}
// 极角排序比较函数
bool angleCmp(const Point& a, const Point& b) {
int qa = quad(a), qb = quad(b);
if (qa != qb) return qa < qb;
return cross(a, b) > 0;
}
int main() {
int N, caseNo = 0;
while (cin >> N, N) {
vector<Point> pts(N);
for (int i = 0; i < N; ++i)
cin >> pts[i].x >> pts[i].y;
// 点数不足 3 时,没有 Bermuda block
if (N < 3) {
cout << fixed << setprecision(2);
cout << "City " << ++caseNo << ": 0.00" << endl;
continue;
}
long long totalInside = 0; // 所有三角形内部点的总数 S
for (int i = 0; i < N; ++i) {
vector<Point> vec;
// 以点 i 为原点,构造其他点的相对向量
for (int j = 0; j < N; ++j) if (i != j)
vec.push_back(pts[j] - pts[i]);
// 按极角排序
sort(vec.begin(), vec.end(), angleCmp);
int m = vec.size();
// 复制一倍处理环形情况
for (int j = 0; j < m; ++j) vec.push_back(vec[j]);
long long bad = 0; // 三点共半平面的三角形数 G(p)
int k = 0;
for (int j = 0; j < m; ++j) {
if (k < j + 1) k = j + 1;
// 找最远的 k 使得角度差严格小于 180°
while (k < j + m && cross(vec[j], vec[k]) > 0) ++k;
long long cnt = k - j - 1;
if (cnt >= 2) bad += cnt * (cnt - 1) / 2;
}
// 注意:这里不需要除以 3,因为每个三角形只被其最小极角的顶点计数一次
long long allTri = 1LL * m * (m - 1) * (m - 2) / 6; // C(N-1, 3)
long long good = allTri - bad; // F(i)
totalInside += good;
}
long long totalTriAll = 1LL * N * (N - 1) * (N - 2) / 6; // C(N, 3)
double ans = (double)totalInside / totalTriAll;
cout << fixed << setprecision(2);
cout << "City " << ++caseNo << ": " << ans << endl;
}
return 0;
}
总结
本题的核心技巧是 极角排序 + 双指针,通过固定原点并利用几何性质将计数问题转化为半平面内的组合计数。关键在于理解“三点共半平面”与“原点在三角形内部”的互补关系,以及如何避免重复计数。时间复杂度 O(N2logN)O(N^2 \log N)O(N2logN) 足以在 N≤1200N \le 1200N≤1200 的范围内高效运行。

2216

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



