UVa 12483 Toboggan of Marbles

题目描述

一家工厂要生产一种弹珠滑梯玩具。该玩具由两根垂直的木杆和一些小的刚性挡板组成。挡板交替连接在左右木杆上:奇数挡板连接在左杆,偶数挡板连接在右杆。一颗小弹珠从最高处的挡板落下,在重力作用下沿着挡板滑动,最终离开玩具。

工厂主已经完成了玩具的设计,包括每个挡板的尺寸、位置和倾斜角度。成千上万个玩具正在中国生产,工厂经理被要求购买弹珠。然而,在订购成千上万颗弹珠之前,他想知道弹珠的最大直径,以确保弹珠不会卡在玩具中间。

工厂经理希望你能编写一个程序,根据玩具规格,确定弹珠能到达玩具末端而不被卡住的最大直径。

输入格式

输入包含多个测试用例。每个测试用例的第一行包含一个整数 NNN ,表示玩具的挡板数量。第二行包含两个整数 LLLHHH ,分别表示两根木杆之间的距离和木杆的高度。左侧木杆位于 XXX 轴的位置 000 ,因此右侧木杆位于 XXX 轴的位置 LLL

接下来的 NNN 行描述每个挡板。挡板从高到低描述,交替描述它们所连接的木杆。最高的挡板(第一个被描述的)一端连接到左侧木杆,第二高的挡板(第二个被描述的)一端连接到右侧木杆,依此类推。奇数挡板连接到左侧木杆,偶数挡板连接到右侧木杆。

每行描述一个挡板,包含三个整数 YiY_iYiXfX_fXfYfY_fYf(Xf,Yf)(X_f, Y_f)(Xf,Yf) 表示挡板自由端(未连接端)的坐标。奇数挡板连接端的坐标为 (0,Yi)(0, Y_i)(0,Yi) ,偶数挡板连接端的坐标为 (L,Yi)(L, Y_i)(L,Yi)

对于所有挡板, Yi>YfY_i > Y_fYi>Yf (即挡板起点和终点之间存在斜率),且挡板的长度小于玩具的宽度。此外,对于两个连续的挡板 AAABBBYfA≥YiBY_{fA} \geq Y_{iB}YfAYiB (即挡板 AAA 的终点高度大于等于挡板 BBB 的起点高度)。假设挡板非常薄,其厚度可以忽略不计。

输出格式

对于每个测试用例,输出一行,包含一个精确到两位小数的数字,表示弹珠能够到达玩具末端而不卡住的最大直径。

限制条件

  • 1≤N≤1031 \leq N \leq 10^31N103
  • 1≤L≤1031 \leq L \leq 10^31L103
  • 1≤H≤1031 \leq H \leq 10^31H103
  • 0<Xf<L0 < X_f < L0<Xf<L
  • 0≤Yi≤H, 0≤Yf≤H0 \leq Y_i \leq H, \, 0 \leq Y_f \leq H0YiH,0YfHYi>YfY_i > Y_fYi>Yf

样例输入

3
6 10
9 3 8
6 2 5
4 3 1
3
5 10
9 3 7
7 2 4
2 3 0

样例输出

2.00
1.41

题目分析与解题思路

问题分析

弹珠在滑梯中运动时可能被卡住的两种情况:

  1. 弹珠与侧壁之间的空隙 :弹珠在挡板上滚动时,不能撞到对侧的木杆。因此,弹珠直径必须小于等于挡板自由端到对侧木杆的水平距离。

  2. 相邻挡板之间的过渡 :弹珠从挡板 AAA 的自由端落到挡板 BBB 上。最狭窄的地方是挡板 AAA 的自由端(一个点)到挡板 BBB 整个线段的最短距离。这是因为弹珠要落到挡板 BBB 上,不能撞到挡板 BBB 的侧面或上表面。

数学模型

1. 自由端到对侧木杆的距离

对于每个挡板 iii (从 000 开始编号):

  • 如果 iii 是偶数(对应题目中的奇数挡板,连接在左杆):自由端在右侧,距离 = L−XfL - X_fLXf
  • 如果 iii 是奇数(对应题目中的偶数挡板,连接在右杆):自由端在左侧,距离 = XfX_fXf

弹珠的最大直径必须小于等于这个距离。

2. 点到线段的最短距离

对于相邻挡板 iiii+1i+1i+1

  • PPP = 挡板 iii 的自由端 (Xfi,Yfi)(X_{f_i}, Y_{f_i})(Xfi,Yfi)
  • 线段 SSS = 挡板 i+1i+1i+1 ,从固定端到自由端
  • 固定端:如果 i+1i+1i+1 是偶数,为 (0,Yi+1)(0, Y_{i+1})(0,Yi+1) ;如果 i+1i+1i+1 是奇数,为 (L,Yi+1)(L, Y_{i+1})(L,Yi+1)– 自由端:(Xfi+1,Yfi+1)(X_{f_{i+1}}, Y_{f_{i+1}})(Xfi+1,Yfi+1)

我们需要计算点 PPP 到线段 SSS 的最短距离。这是一个标准的几何问题,可以通过以下步骤计算:

  1. 计算线段的方向向量 v⃗=(x2−x1,y2−y1)\vec{v} = (x_2 - x_1, y_2 - y_1)v=(x2x1,y2y1)
  2. 如果线段退化为点(方向向量长度为 000 ),则直接计算两点距离
  3. 计算点 PPP 到线段所在直线的投影参数 ttt
    t=(Px−x1)(x2−x1)+(Py−y1)(y2−y1)(x2−x1)2+(y2−y1)2 t = \frac{(P_x - x_1)(x_2 - x_1) + (P_y - y_1)(y_2 - y_1)}{(x_2 - x_1)^2 + (y_2 - y_1)^2} t=(x2x1)2+(y2y1)2(Pxx1)(x2x1)+(Pyy1)(y2y1)
  4. 限制 ttt[0,1][0, 1][0,1] 范围内:
    • 如果 t<0t < 0t<0 ,则最近点是线段起点
    • 如果 t>1t > 1t>1 ,则最近点是线段终点
    • 否则,最近点在线段上
  5. 计算最近点坐标,然后计算点 PPP 到该点的距离

算法步骤

  1. 读取输入数据,存储每个挡板的固定端坐标和自由端坐标
  2. 初始化最大直径 minDiameterminDiameterminDiameter 为一个很大的值
  3. 对于每个挡板,计算自由端到对侧木杆的距离,更新 minDiameterminDiameterminDiameter
  4. 对于每对相邻挡板,计算前一个挡板自由端到后一个挡板线段的最短距离,更新 minDiameterminDiameterminDiameter
  5. 输出 minDiameterminDiameterminDiameter ,保留两位小数

复杂度分析

  • 时间复杂度: O(N)O(N)O(N) ,其中 NNN 是挡板数量。我们需要遍历每个挡板一次,以及每对相邻挡板一次
  • 空间复杂度: O(N)O(N)O(N) ,用于存储挡板信息

代码实现

// Toboggan of Marbles
// UVa ID: 12483
// Verdict: Accepted
// Submission Date: 2025-12-04
// UVa Run Time: 0.010s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

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

// 点结构体
struct Point {
    double x, y;
    Point(double x = 0, double y = 0) : x(x), y(y) {}
};

// 挡板结构体
struct Flap {
    int yi, xf, yf;
    int connX; // 固定端的x坐标
};

// 计算点p到线段ab的最短距离
double pointToSegmentDistance(Point p, Point a, Point b) {
    double dx = b.x - a.x;
    double dy = b.y - a.y;
    
    // 如果线段退化为点
    if (fabs(dx) < 1e-9 && fabs(dy) < 1e-9)
        return sqrt((p.x - a.x) * (p.x - a.x) + (p.y - a.y) * (p.y - a.y));
    
    // 计算投影参数t
    double t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / (dx * dx + dy * dy);
    
    // 限制t在[0,1]范围内
    if (t < 0.0) t = 0.0;
    if (t > 1.0) t = 1.0;
    
    // 计算投影点坐标
    Point proj(a.x + t * dx, a.y + t * dy);
    
    // 返回距离
    return sqrt((p.x - proj.x) * (p.x - proj.x) + (p.y - proj.y) * (p.y - proj.y));
}

int main() {
    int n;
    while (cin >> n) {
        int L, H;
        cin >> L >> H;
        
        vector<Flap> flaps(n);
        for (int i = 0; i < n; i++) {
            cin >> flaps[i].yi >> flaps[i].xf >> flaps[i].yf;
            // 奇数挡板(0-based)连左杆(0),偶数挡板连右杆(L)
            flaps[i].connX = (i % 2 == 0) ? 0 : L;
        }
        
        double minDiameter = 1e9;
        
        // 1. 检查自由端到对侧木杆的距离
        for (int i = 0; i < n; i++) {
            double distance;
            if (i % 2 == 0) { // 奇数挡板,连左杆
                distance = L - flaps[i].xf; // 到右杆的距离
            } else { // 偶数挡板,连右杆
                distance = flaps[i].xf; // 到左杆的距离
            }
            minDiameter = min(minDiameter, distance);
        }
        
        // 2. 检查相邻挡板之间的过渡
        for (int i = 0; i < n - 1; i++) {
            // 挡板i的自由端
            Point p(flaps[i].xf, flaps[i].yf);
            
            // 挡板i+1的线段:从固定端到自由端
            Point a(flaps[i+1].connX, flaps[i+1].yi); // 固定端
            Point b(flaps[i+1].xf, flaps[i+1].yf);    // 自由端
            
            double distance = pointToSegmentDistance(p, a, b);
            minDiameter = min(minDiameter, distance);
        }
        
        cout << fixed << setprecision(2) << minDiameter << endl;
    }
    
    return 0;
}

总结

本题的关键在于识别弹珠可能被卡住的两种几何约束:弹珠与侧壁之间的空隙,以及相邻挡板之间的过渡。通过计算点到线段的距离和点到垂直线的距离,我们可以确定弹珠能够通过的最大直径。这是一个典型的计算几何问题,需要仔细处理几何计算和精度问题。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值