UVa1507/LA5838 Shadow

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 BustersUVa11887 Tetrahedrons and SpheresUVa1367/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;
}
相关推荐
Estella-Ealine17 天前
5.10 周赛vp 2026 ICPC Gran Premio de Mexico 1ra Fecha E题
icpc·2026
所以遗憾是什么呢?18 天前
【题解】Codeforces Round 1097 (Div. 2, Based on Zhili Cup 2026) (致理杯) ABCDEF
数据结构·算法·acm·codeforces·icpc·ccpc·xcpc
漂流瓶jz1 个月前
UVA-1152 和为0的4个值 题解答案代码 算法竞赛入门经典第二版
数据结构·算法·二分查找·题解·aoapc·算法竞赛入门经典·uva
AKDreamer_HeXY1 个月前
QOJ 12255 - 36 Puzzle 题解
数据结构·c++·数学·算法·icpc·qoj
点云登山者1 个月前
7.5 坐标变换——小结
坐标变换·点云入门二十一讲
漂流瓶jz1 个月前
UVA-120 煎饼 题解答案代码 算法竞赛入门经典第二版
数据结构·c++·算法·排序·aoapc·算法竞赛入门经典·uva
所以遗憾是什么呢?2 个月前
【题解】Codeforces Round 1081 (Div. 2)
数据结构·c++·算法·acm·icpc·ccpc·xcpc
漂流瓶jz2 个月前
UVA-10384 推门游戏 题解答案代码 算法竞赛入门经典第二版
数据结构·算法·深度优先·题解·aoapc·算法竞赛入门经典·uva
漂流瓶jz2 个月前
UVA-11846 找座位 题解答案代码 算法竞赛入门经典第二版
数据结构·算法·排序算法·深度优先·aoapc·算法竞赛入门经典·uva