UVa 754 Treasure Hunt

题目分析

问题描述

考古学家需要在一个由直线墙构成的金字塔底层中寻找宝藏。金字塔底层是一个 100×100100 \times 100100×100 的正方形区域,由 444 个外围墙和若干内部墙组成。内部墙总是从一个外壁延伸到另一个外壁,且最多两墙相交于任何点。宝藏位于某个房间内,考古学家希望通过炸开最少数量的门(每次只能在墙的中点炸开)来到达宝藏室。

问题转化

这个问题本质上是一个几何图论问题:

  1. 几何分割:内部墙将矩形区域分割成多个凸多边形房间
  2. 图论建模:每个房间作为图的一个顶点,如果两个房间共享一堵墙,则在它们之间建立边
  3. 最短路径:从宝藏所在房间到任意有外墙的房间的最短路径长度即为需要炸开的最小门数

解题思路

核心算法

我们采用凸多边形切割的方法来构建所有房间,然后使用广度优先搜索(BFS\texttt{BFS}BFS 寻找最短路径。

算法步骤

1. 多边形切割
  • 从整个矩形区域 {Point(0,0), Point(0,100), Point(100,100), Point(100,0)}\texttt{\{Point(0,0), Point(0,100), Point(100,100), Point(100,0)\}}{Point(0,0), Point(0,100), Point(100,100), Point(100,0)} 开始
  • 依次用每堵墙(直线)切割现有的所有多边形
  • 对于每个多边形,用直线将其分为左右两部分,分别形成新的多边形
2. 几何计算
  • 交点计算:使用直线交点公式计算墙与多边形边的交点
  • 点在多边形内:使用射线法判断宝藏点位于哪个多边形内
  • 共享边检测:通过比较多边形的边来判断两个房间是否相邻
3. 图构建与BFS
  • 构建无向图,顶点为房间,边表示房间相邻
  • 从宝藏所在房间开始 BFS\texttt{BFS}BFS,记录到每个房间的距离
  • 当遇到有外墙的房间时,当前距离加 111 即为答案(需要再炸开外墙)

复杂度分析

  • 多边形切割:最坏情况下每堵墙可能将每个多边形一分为二,复杂度为 O(n⋅2n)O(n \cdot 2^n)O(n2n),但 n≤30n \leq 30n30 且实际分割不会如此极端
  • BFS\texttt{BFS}BFSO(V+E)O(V + E)O(V+E),其中 VVV 为房间数,EEE 为相邻关系数
  • 总体复杂度在题目约束下是可接受的

关键实现细节

浮点数精度处理

由于使用浮点数计算几何,需要设置合适的精度容差 EPS=10−9\texttt{EPS} = 10^{-9}EPS=109,并在比较点时使用容差判断。

多边形闭合性

切割后的多边形需要确保首尾顶点相同,形成闭合区域。

重复点处理

几何计算可能产生重复点,需要在构建多边形时去重。

代码实现


// Treasure Hunt
// UVa ID: 754
// Verdict: Accepted
// Submission Date: 2025-11-24
// UVa Run Time: 0.000s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

#include <bits/stdc++.h>
using namespace std;

const double EPS = 1e-9;

struct Point {
    double x, y;
    Point(double x = 0, double y = 0) : x(x), y(y) {}
    bool operator==(const Point& other) const {
        return fabs(x - other.x) < EPS && fabs(y - other.y) < EPS;
    }
};

struct Segment {
    Point p1, p2;
    Segment(Point p1, Point p2) : p1(p1), p2(p2) {}
};

double cross(Point a, Point b, Point c) {
    return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
}

bool onSegment(Point p, Point q, Point r) {
    if (fabs(cross(p, q, r)) > EPS) return false;
    return q.x <= max(p.x, r.x) + EPS && q.x >= min(p.x, r.x) - EPS &&
           q.y <= max(p.y, r.y) + EPS && q.y >= min(p.y, r.y) - EPS;
}

Point getIntersection(Point a, Point b, Point c, Point d) {
    double a1 = b.y - a.y, b1 = a.x - b.x, c1 = a1 * a.x + b1 * a.y;
    double a2 = d.y - c.y, b2 = c.x - d.x, c2 = a2 * c.x + b2 * c.y;
    double det = a1 * b2 - a2 * b1;
    if (fabs(det) < EPS) return Point(-1, -1);
    return Point((b2 * c1 - b1 * c2) / det, (a1 * c2 - a2 * c1) / det);
}

vector<vector<Point>> cutPolygon(const vector<Point>& poly, Point a, Point b) {
    vector<Point> left, right;
    int n = poly.size();
    
    for (int i = 0; i < n; i++) {
        Point p1 = poly[i];
        Point p2 = poly[(i + 1) % n];
        
        double cross1 = cross(a, b, p1);
        double cross2 = cross(a, b, p2);
        
        if (cross1 > -EPS) left.push_back(p1);
        if (cross1 < EPS) right.push_back(p1);
        
        if (cross1 * cross2 < -EPS) {
            Point inter = getIntersection(a, b, p1, p2);
            left.push_back(inter);
            right.push_back(inter);
        }
    }
    
    vector<vector<Point>> result;
    if (left.size() >= 3) result.push_back(left);
    if (right.size() >= 3) result.push_back(right);
    return result;
}

vector<Point> removeDuplicatePoints(const vector<Point>& poly) {
    vector<Point> result;
    for (const Point& p : poly) {
        if (result.empty() || !(result.back() == p)) {
            result.push_back(p);
        }
    }
    if (result.size() >= 2 && result.front() == result.back()) {
        result.pop_back();
    }
    return result;
}

vector<vector<Point>> buildPolygonsByCutting(vector<Segment>& walls) {
    vector<vector<Point>> polygons = {
        {Point(0, 0), Point(0, 100), Point(100, 100), Point(100, 0)}
    };
    
    for (auto& wall : walls) {
        vector<vector<Point>> newPolygons;
        
        for (auto& poly : polygons) {
            vector<vector<Point>> parts = cutPolygon(poly, wall.p1, wall.p2);
            for (auto& part : parts) {
                part = removeDuplicatePoints(part);
                if (part.size() >= 3) {
                    if (!(part.front() == part.back())) {
                        part.push_back(part.front());
                    }
                    newPolygons.push_back(part);
                }
            }
        }
        
        polygons = newPolygons;
    }
    
    return polygons;
}

bool pointInPolygon(Point p, const vector<Point>& poly) {
    int n = poly.size();
    int count = 0;
    for (int i = 0; i < n; i++) {
        Point p1 = poly[i];
        Point p2 = poly[(i + 1) % n];
        
        if (onSegment(p1, p, p2)) return true;
        
        if ((p1.y > p.y) != (p2.y > p.y) &&
            p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x) {
            count++;
        }
    }
    return count % 2 == 1;
}

bool shareWall(const vector<Point>& poly1, const vector<Point>& poly2) {
    for (int i = 0; i < poly1.size(); i++) {
        Point a = poly1[i], b = poly1[(i + 1) % poly1.size()];
        for (int j = 0; j < poly2.size(); j++) {
            Point c = poly2[j], d = poly2[(j + 1) % poly2.size()];
            if ((a == c && b == d) || (a == d && b == c)) {
                return true;
            }
        }
    }
    return false;
}

bool hasExternalWall(const vector<Point>& poly) {
    for (int i = 0; i < poly.size(); i++) {
        Point a = poly[i], b = poly[(i + 1) % poly.size()];
        if ((fabs(a.x) < EPS && fabs(b.x) < EPS) ||
            (fabs(a.x - 100) < EPS && fabs(b.x - 100) < EPS) ||
            (fabs(a.y) < EPS && fabs(b.y) < EPS) ||
            (fabs(a.y - 100) < EPS && fabs(b.y - 100) < EPS)) {
            return true;
        }
    }
    return false;
}

int main() {
    int t;
    cin >> t;
    
    while (t--) {
        int n;
        cin >> n;
        
        vector<Segment> walls;
        for (int i = 0; i < n; i++) {
            double x1, y1, x2, y2;
            cin >> x1 >> y1 >> x2 >> y2;
            walls.push_back(Segment(Point(x1, y1), Point(x2, y2)));
        }
        
        double tx, ty;
        cin >> tx >> ty;
        Point treasure(tx, ty);
        
        vector<vector<Point>> polygons = buildPolygonsByCutting(walls);
        int treasureRoom = -1;
        for (int i = 0; i < polygons.size(); i++) {
            if (pointInPolygon(treasure, polygons[i])) {
                treasureRoom = i;
                break;
            }
        }
        
        if (treasureRoom == -1) {
            cout << "Number of doors = 1" << endl;
        } else {
            int polyCount = polygons.size();
            vector<vector<int>> graph(polyCount);
            
            for (int i = 0; i < polyCount; i++) {
                for (int j = i + 1; j < polyCount; j++) {
                    if (shareWall(polygons[i], polygons[j])) {
                        graph[i].push_back(j);
                        graph[j].push_back(i);
                    }
                }
            }
            
            vector<int> dist(polyCount, -1);
            queue<int> q;
            q.push(treasureRoom);
            dist[treasureRoom] = 0;
            
            int answer = INT_MAX;
            while (!q.empty()) {
                int current = q.front();
                q.pop();
                
                if (hasExternalWall(polygons[current])) {
                    answer = min(answer, dist[current] + 1);
                }
                
                for (int neighbor : graph[current]) {
                    if (dist[neighbor] == -1) {
                        dist[neighbor] = dist[current] + 1;
                        q.push(neighbor);
                    }
                }
            }
            
            if (answer == INT_MAX) answer = 1;
            cout << "Number of doors = " << answer << endl;
        }
        
        if (t > 0) cout << endl;
    }
    
    return 0;
}

总结

本题通过将几何问题转化为图论问题,结合凸多边形切割和广度优先搜索,有效地解决了最小穿墙数问题。算法在保证正确性的同时,具有良好的可读性和可维护性。关键点在于正确处理几何计算的精度问题和多边形的拓扑关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值