礼花:Confetti 是一些大小不一的彩色圆形纸片,人们在派对上、过节时便抛洒它们以示庆祝。落在地上的Confetti会堆叠起来,以至于一部分会被盖住而看不见。给定Confetti的尺寸和位置以及它们的叠放次序,你能计算出有多少Confetti是可以看见的吗?
3.6与平面和空间打交道的计算几何
极限情况
非常需要想象力,某个圆上方的所有圆构成了一个遮罩:
与遮罩的边界相交的圆是可见的,从上图中间的缝看到的也可见,当然也存在完全不被遮罩覆盖的特例:
如果直接考虑下面这个圆可不可见,想想还是挺麻烦的。但是遮罩肯定可见,于是转换思路,给定一个底层圆,判断它的上层圆哪些可见(不要求确定每一个上层圆)?
对某一个圆,计算该圆与其他所有圆的交点,以极角表示。这里未必非要“上层圆”的交点,下层也无妨,因为待会儿枚举中点的时候是从上往下取第一个的,这样实际上还是取了上层遮罩。多枚举没关系,只要不少枚举就行。
对于所有例子(包含特例),都认为0和2π是两个交点(实际上是同一个点,对应一段长度为0的弧)。排序后相邻两个交点的中点取稍微靠内与靠外的两个点,这两个点从上到下落入的第一个圆(遮罩中的某个圆)一定是可见的。此时相邻两个交点构成的弧不是上图的橙色高亮部分,而是被分割的虚线部分。
至于极角的计算,依然一图流:
#include <iostream> #include <algorithm> #include <vector> #include <complex> #include <cmath> using namespace std; typedef complex<double> P; #define M_PI 3.14159265358979323846 #define EPS 4E-13 //************************************ // Returns: 将极角转为[0, 2pi)之间 // Parameter: double r //************************************ double normalize(double r) { while (r < 0.0) r += 2 * M_PI; while (r >= 2 * M_PI) r -= 2 * M_PI; return r; } //************************************ // Returns: 从上到下点落入的第一个圆的下标, // 点没被覆盖则返回-1 // Parameter: vector<P> & points // Parameter: vector<double> & rs //************************************ int hit_test(P p, vector<P> &points, vector<double> &rs) { for (int i = rs.size() - 1; i >= 0; --i) if (abs(points[i] - p) < rs[i]) return i; return -1; } ///////////////////////////SubMain////////////////////////////////// int main(int argc, char *argv[]) { #ifndef ONLINE_JUDGE freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); #endif int n; while (scanf("%d", &n), n) { vector<P> points; vector<double> rs; for (int i = 0; i < n; ++i) { double x, y, r; scanf("%lf%lf%lf", &x, &y, &r); points.push_back(P(x, y)); rs.push_back(r); } vector<bool> visible(n, false); for (int i = 0; i < n; ++i) // 从最下面一个开始处理 { vector<double> rads; // 与其他圆相交的弧的极角 rads.push_back(0.0); rads.push_back(2.0 * M_PI); for (int j = 0; j < n; j++) { double a = rs[i]; double b = abs(points[j] - points[i]); double c = rs[j]; if (a + b < c || a + c < b || b + c < a) // 构不成三角形,两圆不相交 continue; double d = arg(points[j] - points[i]); double e = acos((a * a + b * b - c * c) / (2 * a * b)); rads.push_back(normalize(d + e)); // 用极角表示的圆弧的两个端点 rads.push_back(normalize(d - e)); } sort(rads.begin(), rads.end()); for (int j = 0; j < rads.size() - 1; j++) { double rad = (rads[j + 1] + rads[j]) / 2.0; // 中点与圆心构成的极角 for (int k = -1; k <= 1; k += 2) // 中点在圆弧内外的两个点 { int t = hit_test(P(points[i].real() + (rs[i] + EPS * k) * cos(rad), points[i].imag() + (rs[i] + EPS * k) * sin(rad)), points, rs); if (t != -1) visible[t] = true; } } } printf("%d\n", count(visible.begin(), visible.end(), true)); } #ifndef ONLINE_JUDGE fclose(stdin); fclose(stdout); system("out.txt"); #endif return 0; } ///////////////////////////End Sub//////////////////////////////////
13876375 | hankcs | 1418 | Accepted | 224K | 63MS | C++ | 2525B | 2015-02-10 03:17:05 |
Reference
知识共享署名-非商业性使用-相同方式共享:码农场 » POJ 1418 Viva Confetti 题解 《挑战程序设计竞赛》