UVa1507/LA5838 Shadow
题目链接
本题是2011年icpc亚洲区域赛福州赛区的J题
题意
在宇宙的某个地方,Orez 是一颗拥有高度文明和先进技术的行星。但不幸的是,一颗靠近 Orez 的超新星爆发了,并释放出一种可能危及 Orez 文明的异常辐射。
这颗行星上有一支庞大的太空舰队。舰队拥有一艘战斗巡洋舰和若干架战斗机。一架战斗机可以产生一个球形的磁力护盾,辐射无法穿透该护盾。当舰队在太空中时,如果辐射照射过来,战斗巡洋舰和磁力护盾可以在地面上投下阴影。
当辐射击中行星时,Orez 上的人们只有进入舰队的阴影区域才能幸免。所以我们想知道安全阴影的面积。
为了简化问题,可以认为:地面是无限平面,辐射是平行光,战斗机是一个点,战斗巡洋舰是一个三维凸包。
输入格式
输入包含多个测试用例。
对于每个测试用例,第一行包含五个整数: N 、 M 、 D x 、 D y 和 D z N、M、D_x、D_y\;和\;D_z N、M、Dx、Dy和Dz。
- N 是战斗机的数量。
- 后面会给出 M 个在战斗巡洋舰上或内部的点,这些点的凸包即为战斗巡洋舰的形状。
- ( D x , D y , D z ) (Dx,Dy,Dz) (Dx,Dy,Dz) 是辐射的方向向量,且 D z D_z Dz 为负数。
接下来的 N 行,每行包含四个整数 X i 、 Y i 、 Z i 和 P i X _i、Y _i、Z_i\;和\;P_i Xi、Yi、Zi和Pi,代表一架战斗机。 ( X i , Y i , Z i ) (X_i,Y_i,Z_i) (Xi,Yi,Zi) 是战斗机的坐标(也是其制造的球形护盾的中心), P i P_i Pi 是护盾的半径。
再接下来的 M 行,每行包含三个整数 X i 、 Y i 和 Z i X_i、Y_i\;和\;Z _i Xi、Yi和Zi,代表战斗巡洋舰上或内部的一个点。
所有 X i 、 Y i X_i、Y_ i Xi、Yi 的范围是 0 , 100 0,100 0,100;所有 Z i Z_i Zi 的范围是 20 , 100 20,100 20,100; 0 ≤ P i ≤ 20 0≤Pi≤20 0≤Pi≤20; N + M ≤ 500 N+M≤500 N+M≤500。地面是平面 Z = 0 Z=0 Z=0。
请注意,多架战斗机可能位于同一位置,战斗机可能位于巡洋舰内部或表面上。
输入以 0 0 0 0 0 结束。
输出格式
对于每个测试用例,输出一行阴影的面积。结果应四舍五入到小数点后 4 位。。
分析
本题是2010年福州赛区UVa1488/LA5100 Shade of Hallelujah Mountain 的姊妹篇,两题的主要套路都是通过坐标变换将问题转化为二维凸包。本题还需要用离散化扫描法 求圆和凸多边形的面积并。相关知识点可参考UVa1313/LA2693 Ghost Busters、UVa11887 Tetrahedrons and Spheres、UVa1367/LA3532 Nuclear Plants,下面围绕细节、坑点和测试数据生成展开细说。
细节与坑点
坐标变换的正确流程:先将输入坐标都投影到法向量为 ( D x , D y , D z ) (Dx,Dy,Dz) (Dx,Dy,Dz) 的平面上,再做坐标变换将此平面上的三维点 ( x , y , z ) (x,y,z) (x,y,z) 转换成 z ′ = 0 {z}'=0 z′=0 的二维点 ( x ′ , y ′ ) ({x}',{y}') (x′,y′)。
离散化的细节与坑点:求两圆的交点要先排除相离 和内含 (题面交代的 多架战斗机可能位于同一位置 就是典型的两圆内含)的情况,相切时切点也需要加入离散化点集;求圆与线段的交点要先排除圆与直线不相交的请况,再检查确保交点在线段内。
注意 :相切以及点在圆周上的极限情况下,由于浮点数的误差,可能出现 r 2 − d 2 < 0 、 r 2 − x 2 < 0 r^2-d^2<0、r^2-x^2<0 r2−d2<0、r2−x2<0 但数值很接近零的情况,那么求 r 2 − d 2 、 r 2 − x 2 \sqrt {r^2-d^2}、\sqrt {r^2-x^2} r2−d2 、r2−x2 时需要截断成 0,即 s q r t ( m a x ( x , 0. ) ) sqrt(max(x,0.)) sqrt(max(x,0.))。反三角也需要截断,即 a s i n ( m a x ( m i n ( v , 1. ) , − 1. ) 、 a c o s ( m a x ( m i n ( v , 1. ) , − 1. ) asin(max(min(v,1.),-1.)、acos(max(min(v,1.),-1.) asin(max(min(v,1.),−1.)、acos(max(min(v,1.),−1.)。
测试数据生成
提供一份python脚本生成测试数据,三维立体仅生成四面体和长方体两种简单情形,足够debug发现问题了。
python
from random import randint
def rand1():
return randint(0, 100)
def rand2():
return randint(20, 100)
def rand3():
return randint(-100, 100)
def rand4():
return randint(-100, -1)
def rand_p():
return rand1(), rand1(), rand2()
def area(a, b, c):
x1, y1, z1, x2, y2, z2 = b[0] - a[0], b[1] - a[1], b[2] - a[2], c[0] - a[0], c[1] - a[1], c[2] - a[2]
return y1*z2 - y2*z1, z1*x2 - z2*x1, x1*y2 - x2*y1
def vol(a, b, c, d):
e = area(a, b, c)
x, y, z = d[0] - a[0], d[1] - a[1], d[2] - a[2]
return abs(e[0]*x + e[1]*y + e[2]*z)
def gen1():
p1, p2, p3 = rand_p(), rand_p(), rand_p()
while area(p1, p2, p3) == (0, 0, 0):
p1, p2, p3 = rand_p(), rand_p(), rand_p()
p4 = rand_p()
while vol(p1, p2, p3, p4) == 0:
p4 = rand_p()
n, v, p, f = randint(0, 5), vol(p1, p2, p3, p4), [p1, p2, p3, p4], []
for _ in range(n):
x = rand_p()
while vol(p1, p2, p3, x) + vol(p1, p2, p4, x) + vol(p1, p3, p4, x) + vol(p2, p3, p4, x) != v:
x = rand_p()
f.append((x[0], x[1], x[2], randint(1, 20)))
return n, 4, rand3(), rand3(), rand4(), f, p
def gen2():
x1, x2, y1, y2, z1, z2 = rand1() , rand1(), rand1(), rand1(), rand2(), rand2()
xa, xb, ya, yb, za, zb = min(x1, x2), max(x1, x2), min(y1, y2), max(y1, y2), min(z1, z2), max(z1, z2)
n = randint(0, min((xb-xa+1)*(yb-ya+1)*(zb-za+1), 5))
p = [(xa, ya, za), (xb, ya, za), (xb, yb, za), (xa, yb, za), (xa, ya, zb), (xb, ya, zb), (xb, yb, zb), (xa, yb, zb)]
f = []
for _ in range(n):
x, y, z = rand_p()
while x < xa or x > xb or y < ya or y > yb or z < za or z > zb:
x, y, z = rand_p()
f.append((x, y, z, randint(1, 20)))
return n, 8, rand3(), rand3(), rand4(), f, p
def gen():
if randint(0, 1) == 1:
return gen1()
return gen2()
if __name__ == '__main__':
with open('in.txt', 'w') as fin:
for _ in range(10):
n, m, a, b, c, f, p = gen()
fin.write(f'{n} {m} {a} {b} {c}\n')
for v in f:
x, y, z, r = v
fin.write(f'{x} {y} {z} {r}\n')
for v in p:
x, y, z = v
fin.write(f'{x} {y} {z}\n')
fin.write('0 0 0 0 0\n')
AC 代码
cpp
#include <iostream>
#include <iomanip>
#include <cmath>
#include <algorithm>
using namespace std;
#define eps 1e-12
#define M 502
#define sqr(x) sqrt(max(x, 0.))
struct aseg {
double y; bool u; int s;
bool operator< (const aseg& rhs) const {
return y < rhs.y;
}
} g[M<<1];
struct Point {
double x, y;
Point(double x = 0., double y = 0.): x(x), y(y) {}
} f[M], p[M], ch[M];
int r[M], m, n, a, b, c; double x[M*M], na, nb, nc, c1, s1, c2, s2;
typedef Point Vector;
Vector operator- (const Vector& A, const Vector& B) {
return Vector(A.x - B.x, A.y - B.y);
}
bool operator< (const Point& a, const Point& b) {
return a.x < b.x || (a.x == b.x && a.y < b.y);
}
double Cross(const Vector& A, const Vector& B) {
return A.x * B.y - A.y * B.x;
}
void convert2d(Point& p) {
int x, y, z; cin >> x >> y >> z;
double d = x*na + y*nb + z*nc, xp = x - d*na, yp = y - d*nb, zp = z - d*nc;
p.x = (yp*s1 + zp*c1) * s2 + xp*c2; p.y = yp*c1 - zp*s1;
}
void circle_intersect_seg(const Point& c, int r, const Point& a, const Point& b) {
double x1 = b.x - a.x, y1 = b.y - a.y, x2 = c.x - a.x, y2 = c.y - a.y,
s = sqrt(x1*x1 + y1*y1), d = abs(x1*y2 - x2*y1) / s;
if (d >= r+eps) return;
double p = (x1*x2 + y1*y2) / s; d = sqr(r*r - d*d);
if (p-d > eps && p-d < s-eps) x[::a++] = a.x + x1*(p-d)/s;
if (p+d > eps && p+d < s-eps) x[::a++] = a.x + x1*(p+d)/s;
}
void circle_intersect_circle(const Point& c1, int r1, const Point& c2, int r2) {
double dx = c2.x - c1.x, dy = c2.y - c1.y, d = sqrt(dx*dx + dy*dy);
if (d >= r1+r2+eps || d <= abs(r1-r2)-eps) return;
double d1 = .5 * (d + (r1*r1 - r2*r2)/d), s = sqr(r1*r1-d1*d1), x0 = c1.x + dx*d1/d;
d = dy*s/d; x[a++] = x0 - d; x[a++] = x0 + d;
}
double area(const Point& c, int r, double x1, double x2) {
x1 -= c.x; x2 -= c.x;
double r2 = r*r, y1 = sqr(r2 - x1*x1), y2 = sqr(r2 - x2*x2), cc = abs(x1*y2 - x2*y1);
return .5 * (asin(cc / r2) * r2 - cc);
}
void solve() {
double s = b*b + c*c, t = sqrt(a*a + s);
s = sqrt(s); na = a/t; nb = b/t; nc = c/t; c1 = -c/s; s1 = -b/s; c2 = s/t; s2 = a/t;
for (int i=0; i<n; ++i) convert2d(f[i]), cin >> r[i];
for (int i=0; i<m; ++i) convert2d(p[i]);
sort(p, p+m); c = 0;
for (int i=0; i<m; ++i) {
while (c > 1 && Cross(ch[c-1]-ch[c-2], p[i]-ch[c-2]) <= 0.) --c;
ch[c++] = p[i];
}
for (int i=m-2, k=c; i>=0; --i) {
while (c > k && Cross(ch[c-1]-ch[c-2], p[i]-ch[c-2]) <= 0.) --c;
ch[c++] = p[i];
}
-- c;
for (int i=a=0; i<c; ++i) x[a++] = ch[i].x;
for (int i=0; i<n; ++i) {
x[a++] = f[i].x - r[i]; x[a++] = f[i].x; x[a++] = f[i].x + r[i];
for (int j=0; j<c; ++j) circle_intersect_seg(f[i], r[i], ch[j], ch[j+1]);
for (int j=i+1; j<n; ++j) circle_intersect_circle(f[i], r[i], f[j], r[j]);
}
sort(x, x+a); s = 0.;
for (int i=1; i<a; ++i) if (x[i] - x[i-1] > eps) {
double v = .5 * (x[i]+x[i-1]); int t = 0;
for (int j=0; j<c; ++j) {
double x1 = min(ch[j].x, ch[j+1].x), x2 = max(ch[j].x, ch[j+1].x);
if (v < x1+eps || v > x2-eps) continue;
double u = (ch[j].x - v) / (ch[j].x - ch[j+1].x), y = u * ch[j+1].y + (1. - u) * ch[j].y;
t ? g[t++] = {y, true, -1} : g[t++] = {y, false, -1};
}
for (int j=0; j<n; ++j) if (abs(v - f[j].x) < r[j]-eps) {
double x1 = v-f[j].x, y = sqr(r[j]*r[j] - x1*x1);
g[t++] = {f[j].y - y, false, j}; g[t++] = {f[j].y + y, true, j};
}
sort(g, g+t);
for (int j=0, c=0, p; j<t; ++j) {
if (g[j].u) {
if (--c == 0) {
double y1, y2;
if (g[p].s >= 0) {
int k = g[p].s; double x1 = x[i-1]-f[k].x, x2 = x[i]-f[k].x, r2 = r[k]*r[k];
y1 = f[k].y - .5 * (sqr(r2-x1*x1) + sqr(r2-x2*x2));
s -= area(f[k], r[k], x[i-1], x[i]);
} else y1 = g[p].y;
if (g[j].s >= 0) {
int k = g[j].s; double x1 = x[i-1]-f[k].x, x2 = x[i]-f[k].x, r2 = r[k]*r[k];
y2 = f[k].y + .5 * (sqr(r2-x1*x1) + sqr(r2-x2*x2));
s -= area(f[k], r[k], x[i-1], x[i]);
} else y2 = g[j].y;
s -= (x[i] - x[i-1]) * (y2 - y1);
}
} else if (c++ == 0) p = j;
}
}
cout << s / nc << endl;
}
int main() {
cout << fixed << setprecision(4);
while (cin >> n >> m >> a >> b >> c && (n || m || a || b || c)) solve();
return 0;
}