UVa1318/LA2797 Monster Trap

题目链接

         本题是2003年ICPC亚洲区域赛会津(日本)赛区H

题意

        给出一些线段障碍,你的任务是判断怪物能否逃到无穷远处。如下图所示,左图无法逃出,右图的可以逃出。

        输入包含多组数据。每组数据第一行为整数n(1≤n≤100),即线段条数。以下n 行每行4 个整数,即一条线段两端的坐标。假定线段的长度均为正,坐标绝对值不超过50。假设任意两条线段最多只有一个公共点,无三线共点。任意两个交点的距离大于1e-5。输入结束标志为n=0。        

分析

       《训练指南》上的例题,按照其题解做,发现一个套模板的坑点:本题卡阈值!

        这里给出《训练指南》代码仓库上的源码(第10行)来说明坑点:

const double eps = 1e-12;

        如过把eps改为大于1e-12或者小于1e-14的值(比如1e-111e-15)再提交则WA,而eps写成1e-121e-131e-14则AC。

        根本原因在《训练指南》给出的计算几何模板里面的dcmpSegmentProperIntersectionOnSegment三个函数这里。

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

bool SegmentProperIntersection(const Point& a1, const Point& a2, const Point& b1, const Point& b2) {
    double c1 = Cross(a2 - a1, b1 - a1), c2 = Cross(a2 - a1, b2 - a1);
    double c3 = Cross(b2 - b1, a1 - b1), c4 = Cross(b2 - b1, a2 - b1);
    return dcmp(c1) * dcmp(c2) < 0 && dcmp(c3) * dcmp(c4) < 0;
}

bool OnSegment(const Point& p, const Point& a1, const Point& a2) { // 不含端点
    return dcmp(Cross(a1 - p, a2 - p)) == 0 && dcmp(Dot(a1 - p, a2 - p)) < 0;
}

        OnSegment函数对dcmp的阈值eps要求低一些(不可太大也不可太小,中间范围即可),但本题的做法涉及把每条线段两端延长一点点(按题意延长量为1e-6合适,因为题木交代两个交点的距离在1e-5量级以上),这样就导致SegmentProperIntersection里面叉乘的结果可能很小,极端数据会小于eps。

        说一下解决方案:SegmentProperIntersection里面对叉乘直接判断符号,不用阈值。

int sign(double x) {
    return x==0. ? 0 : (x < 0. ? -1 : 1);
}

bool SegmentProperIntersect(const Point& a1, const Point& a2, const Point& b1, const Point& b2) {
    double c1 = Cross(a2 - a1, b1 - a1), c2 = Cross(a2 - a1, b2 - a1);
    double c3 = Cross(b2 - b1, a1 - b1), c4 = Cross(b2 - b1, a2 - b1);
    // return dcmp(c1) * dcmp(c2) < 0 && dcmp(c3) * dcmp(c4) < 0;
    // 部分卡精度的题目需要直接判断正负号
    return sign(c1) * sign(c2) < 0 && sign(c3) * sign(c4) < 0;
}

        附一份测试数据和用python写的画图可视化分析数据的脚本方便读者debug。

AC代码

#include <iostream>
#include <cstring>
#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) {}
    void Normalize() {
        double l = sqrt(x*x + y*y); x /= l; y /= l;
    }
};
typedef Point Vector;

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

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

Vector operator* (const Vector& A, double p) {
    return Vector(A.x * p, A.y * p);
}

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;
}

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

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

int sign(double x) {
    return x==0. ? 0 : (x < 0. ? -1 : 1);
}

bool SegmentProperIntersect(const Point& a1, const Point& a2, const Point& b1, const Point& b2) {
    double c1 = Cross(a2 - a1, b1 - a1), c2 = Cross(a2 - a1, b2 - a1);
    double c3 = Cross(b2 - b1, a1 - b1), c4 = Cross(b2 - b1, a2 - b1);
    return sign(c1) * sign(c2) < 0 && sign(c3) * sign(c4) < 0;
}

bool OnSegment(const Point& p, const Point& a1, const Point& a2) {
    return dcmp(Cross(a1 - p, a2 - p)) == 0 && dcmp(Dot(a1 - p, a2 - p)) < 0;
}

#define N 205
Point p[N]; int x[N], q[N], n, t; bool vis[N];

bool NotOnAnySegment(const Point& x) {
    for (int i=0; i<n; ++i) if (OnSegment(x, p[i], p[i+n])) return false;
    return true;
}

bool NotIntersectWithAnySegment(int u, int v) {
    for (int i=0; i<n; ++i) if (SegmentProperIntersect(p[i], p[i+n], p[u], p[v])) return false;
    return true;
}

bool solve() {
    memset(vis, t = 0, sizeof(vis));
    for (int i=0; i<n; ++i) {
        cin >> p[i].x >> p[i].y >> p[i+n].x >> p[i+n].y;
        Vector v = p[i+n] - p[i]; v.Normalize(); v = v*1e-6;
        p[i].x -= v.x; p[i].y -= v.y; p[i+n].x += v.x; p[i+n].y += v.y;
    }
    p[n<<1].x = p[n<<1].y = 0.; p[(n<<1)+1].x = -60.; p[(n<<1)+1].y = 60.; x[t++] = n<<1; x[t++] = (n<<1)+1;
    for (int i=0; i<n; ++i) {
        if (NotOnAnySegment(p[i])) x[t++] = i;
        if (NotOnAnySegment(p[i+n])) x[t++] = i+n;
    }
    int head = 0, tail = 1; q[0] = 0;
    while (head < tail) {
        int u = q[head++];
        for (int v=1; v<t; ++v) if (!vis[v] && NotIntersectWithAnySegment(x[u], x[v])) {
            if (v == 1) return false;
            vis[q[tail++] = v] = 1;
        }
    }
    return true;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    while (cin >> n && n) cout << (solve() ? "yes" : "no") << endl;
    return 0;
}
  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值