UVa11726 Crime Scene

题目链接

         UVa11726 - Crime Scene

题意

        给定n(n≤100)个物体,每个物体都是一个圆或者k(k≤10)边形,用长度尽量小的绳子把它们包围起来。

分析

        孟加拉国Manzurur Rahman Khan (Sidky)大神出的难题,大神毕业于BUET(Bangladesh University of Engineering and Technology)。

        几何图形集合的凸包问题(凸包有两个最优性:长度最短;面积最小),本题的基本图形有圆,所以可以这么做

        求出所有点和圆的切点,然后所有的点和原有的点一起做一次凸包,求出凸包的边长,如果连续的两个点都是同一个圆上的切点,计算长度的方式就要变成求弧长。

        这里不细说取巧的方法,而是详细探讨几何图形集合的凸包问题的一般求法,姑且叫滚边法吧:在所有图形中找到最低点,多个图形包含最低点时任取一个作为起点即可,初始时假想从起点水平往右的向量作为当前向量,找出逆时针最小旋转量使其刚好贴到下一个图形某点上,下一个图形如果是圆,则可以绕着切点逆时针转一定角度(注意:可能会转超过180度)再贴到其他图形的某一点上,依此不停滚边操作,直到回贴到起点。

        接下来说一些细节,先看这一份数据

10
2
c 0 0 2
p 4 -1 2 1 2 1 -2 -1 -2
1
c 0 0 3
2
c 0 0 2
c 10 10 1
5
c 0 0 1
c 10 0 1
c 0 10 1
c 10 10 1
p 4 4 4 5 5 6 4 5 8
3
c 0 0 2
c 2 2 2
p 3 -1 2 1 4 3 -1
3
p 3 -2 5 2 5 4 8
c 5 -8 7
c -10 -1 9
4
p 2 -1 -8 -9 10
p 1 -5 -7
c -1 2 9
c 7 -7 1
3
p 2 9 2 -1 -5
p 4 9 -4 8 -4 -4 7 -3 -7
c 2 3 7
5
p 2 5 2 -1 -9
c 9 -7 8
p 2 -9 9 -2 9
c -4 1 8
p 3 -10 -9 4 3 -7 -9
6
p 1 1 -10
p 1 2 5
p 1 -5 2
c 3 -1 1
p 7 8 -4 -8 9 -1 10 -4 -5 -6 9 -9 -6 -9 -1
c -3 -6 4

        其正确输出应该是:

Case #1: 13.148009
Case #2: 18.849556
Case #3: 37.779789
Case #4: 46.283185
Case #5: 18.749469
Case #6: 87.092793
Case #7: 63.571176
Case #8: 51.636719
Case #9: 84.282670
Case #10: 59.374851

        以上结果可以利用可视化脚本画图再手算检验。

        后面几条数据容易出错,调试发现坑在double精度及反三角函数acos这里,acos函数传参必须严格在[-1,1]区间,否则会得到nan,因此可以这样处理:

double safe_acos(double v) {
     return acos(min(max(v, -1.), 1.));
}

        但更推荐另外一种方式:计算余弦值的过程中用long double保精度因此能严格在[-1,1]区间,这样就可以直接调用acos了(可以将下面AC代码里面的long double全部换成double再提交验证,会WA)。

AC代码

#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;

#define eps 1e-10
struct Point {
    double x, y;
    Point(double x = 0., double y = 0.): x(x), y(y) {}
};
typedef Point Vector;

Vector operator- (const Vector& A, const Vector& B) {
    return Vector(A.x - B.x, A.y - B.y);
}

int dcmp(double x) {
    return abs(x) < eps ? 0 : (x < 0. ? -1 : 1);
}

bool operator== (const Point& a, const Point& b) {
    return dcmp(a.x - b.x) == 0 && dcmp(a.y - b.y) == 0;
}

double Cross(const Vector& A, const Vector& B) {
    return A.x * B.y - A.y * B.x;
}

double Dot(const Vector& A, const Vector& B) {
    return A.x * B.x + A.y * B.y;
}

double Lsq(const Vector& A) {
    return A.x*A.x + A.y*A.y;
}

#define N 102
Point c[N], p[11*N]; double r[N], pi2 = M_PI + M_PI; int n; bool esc0[N], esc[11*N];

double solve() {
    cin >> n;
    int a = 0, b = 0;
    while (n--) {
        char ch; cin >> ch;
        if (ch == 'p') {
            int k; cin >> k;
            while (k--) cin >> p[b].x >> p[b].y, esc[b++] = false;
        } else cin >> c[a].x >> c[a].y >> r[a], esc0[a++] = false;
    }
    for (int i=0; i<a; ++i) if (!esc0[i]) for (int j=i+1; j<a; ++j) {
        if (dcmp(r[i]-r[j]) <= 0 && dcmp(sqrt(Lsq(c[i]-c[j])) - r[j]+r[i]) <= 0) {
            esc0[i] = true; break;
        }
        if (dcmp(r[i]-r[j]) > 0 && dcmp(sqrt(Lsq(c[i]-c[j])) - r[i]+r[j]) <= 0) esc0[j] = true;
    }
    for (int i=0; i<b; ++i) if (!esc[i]) {
        for (int j=i+1; j<b; ++j) if (p[i] == p[j]) {
            esc[i] = true; break;
        }
        if (esc[i]) continue;
        for (int j=0; j<a; ++j) if (dcmp(sqrt(Lsq(p[i]-c[j])) - r[j]) <= 0) {
            esc[i] = true; break;
        }
    }
    for (int j=a, i=a=0; i<j; ++i) if (!esc0[i]) c[a] = c[i], r[a++] = r[i];
    for (int j=b, i=b=0; i<j; ++i) if (!esc[i]) p[b++] = p[i];
    if (a == 1 && b == 0) return pi2 * r[0];
    if (a == 0 && b < 2) return 0.;
    int s = 0; bool f = a>0;
    for (int i=0; i<a; ++i) if (c[i].y-r[i] < c[s].y-r[s]) s = i;
    for (int i=0; i<b; ++i) {
        if (f) {
            if (p[i].y < c[s].y-r[s]) s = i, f = false;
        } else if (p[i].y < p[s].y) s = i;
    }
    double t = 0., g = 0.; int x = s; bool u = f; Vector v(1.);
    while (true) {
        int y = -1; bool q; long double h;
        if (u) {
            for (int i=0; i<a; ++i) if (i != x) {
                Vector z = c[i] - c[x]; long double l = sqrt(Lsq(z)), sin = (r[i]-r[x])/l, cos = sqrt(1.-sin*sin);
                Vector w(z.x*cos + z.y*sin, z.y*cos - z.x*sin);
                cos = dcmp(Cross(v, w)) < 0 ? -2. - Dot(v, w) / l : Dot(v, w) / l;
                if (y < 0 || dcmp(cos-h) > 0) y = i, h = cos, q = true;
            }
            for (int i=0; i<b; ++i) {
                Vector z = p[i] - c[x]; long double l = sqrt(Lsq(z)), sin = r[x]/l, cos = sqrt(1.-sin*sin);
                Vector w(z.x*cos - z.y*sin, z.y*cos + z.x*sin);
                cos = dcmp(Cross(v, w)) < 0 ? -2. - Dot(v, w) / l : Dot(v, w) / l;
                if (y < 0 || dcmp(cos-h) > 0) y = i, h = cos, q = false;
            }
            h = h < -1. ? pi2-acos(-2.-h) : acos(h);
            if (f && x==s && dcmp(g + h - pi2) >= 0) return t + (pi2-g)*r[s];
            t += h*r[x] + (q ? sqrt(Lsq(c[y] - c[x]) - (r[y]-r[x])*(r[y]-r[x])) : sqrt(Lsq(p[y] - c[x]) - r[x]*r[x]));
            if (!f && !q && y==s) return t;
            g += h; x = y; u = q; v.x = cos(g); v.y = sin(g);
        } else {
            for (int i=0; i<a; ++i) {
                Vector z = c[i] - p[x]; long double l = sqrt(Lsq(z)), sin = r[i]/l, cos = sqrt(1.-sin*sin);
                Vector w(z.x*cos + z.y*sin, z.y*cos - z.x*sin);
                if (dcmp(Cross(v, w)) < 0) continue;
                cos = Dot(v, w) / l;
                if (y < 0 || dcmp(cos-h) > 0) y = i, h = cos, q = true;
            }
            for (int i=0; i<b; ++i) if (i != x) {
                Vector w = p[i] - p[x];
                if (dcmp(Cross(v, w)) < 0) continue;
                long double cos = Dot(v, w) / sqrt(Lsq(w));
                if (y < 0 || dcmp(cos-h) > 0) y = i, h = cos, q = false;
            }
            h = acos(h);
            t += q ? sqrt(Lsq(c[y] - p[x]) - r[y]*r[y]) : sqrt(Lsq(p[y] - p[x]));
            if (!f && !q && y==s) return t;
            g += h; x = y; u = q; v.x = cos(g); v.y = sin(g);
        }
    }
    return t;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(6);
    int t; cin >> t;
    for (int kase=1; kase<=t; ++kase) cout << "Case #" << kase << ": " << solve() << endl;
    return 0;
}

  • 30
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值