题目描述

TRobots\texttt{TRobots}TRobots 公司制造并维护一种名为“游览机器人”的特殊机器人。这种机器人在笛卡尔平面中按照预定路径移动并执行任务。一个任务由至少两个点构成,机器人必须按照以下规则依次访问这些点:

  1. 机器人在第一个点处开始,并面向第二个点;
  2. 机器人沿着当前面向的方向移动;
  3. 当到达序列中的一个点时,机器人会逆时针旋转一个角度 α\alphaα0≤α<2π0 \leq \alpha < 2\pi0α<2π),直到面向序列中的下一个点(注意:序列是循环的,即最后一个点的下一个点是第一个点);
  4. 机器人在返回第一个点后结束任务,此时它面向第二个点。

由于机器人在整个游览过程中会不断地旋转,最终它会完成整数圈旋转。 TRobots\texttt{TRobots}TRobots 公司认为这个圈数很重要,因为它决定了机器人何时需要进行维护。你的任务是:给定一个游览任务(即一系列点的坐标),计算机器人在完成该任务时总共旋转了多少圈。

输入格式

输入包含多个测试用例。每个测试用例的格式如下:

  • 第一行:一个整数 NNN1<N<10001 < N < 10001<N<1000),表示游览中需要访问的点的数量。
  • 接下来的 NNN 行:每行包含两个整数 xxxyyy−106≤x,y≤106-10^{6} \leq x, y \leq 10^{6}106x,y106),表示一个点的坐标。保证相邻的点(包括最后一个点和第一个点)不相同。

输入以 N=0N = 0N=0 结束。

输出格式

对于每个测试用例,输出一行,包含一个整数,表示机器人完成该游览任务所旋转的圈数。

样例输入

4
0 0
0 1
1 1
1 0
0

样例输出

1

题目分析

这道题的核心在于理解机器人的运动方式,并将其转化为几何问题。

1. 运动过程分析

机器人从第一个点出发,沿着当前面向的方向移动到下一个点。到达一个点后,它会逆时针旋转,直到面向下一个点,然后继续移动。这个过程会一直持续,直到它返回起点。

如果我们把机器人访问的所有点按顺序连起来,就得到了一个闭合多边形。机器人在每个顶点处都需要旋转一个角度,这个角度就是多边形在该顶点处的外角

2. 数学原理

对于一个简单的闭合多边形(不自交),其外角之和总是 360∘360^\circ360 (即 2π2\pi2π 弧度)。然而,如果多边形是复杂的(例如自交、有凹陷等),那么外角之和就不再是 2π2\pi2π ,而是 2π×k2\pi \times k2π×k ,其中 kkk 是一个整数,称为多边形的旋转数winding number\texttt{winding number}winding number)或圈数

旋转数 kkk 表示:当我们绕着多边形走一圈时,我们的方向总共旋转了多少个完整的 360∘360^\circ360 。顺时针旋转会减少 kkk ,逆时针旋转会增加 kkk 。在本题中,机器人总是逆时针旋转,所以 kkk 一定是非负整数。

3. 问题转化

因此,问题转化为:给定一个闭合多边形的顶点坐标(按访问顺序),计算这个多边形的旋转数。

具体来说,我们需要:

  1. 对于每个顶点 PiP_iPi ,计算从 Pi−1P_{i-1}Pi1PiP_iPi 的向量 u⃗\vec{u}u ,和从 PiP_iPiPi+1P_{i+1}Pi+1 的向量 v⃗\vec{v}v
  2. 计算从 u⃗\vec{u}u 逆时针旋转到 v⃗\vec{v}v 的角度 αi\alpha_iαi0≤αi<2π0 \leq \alpha_i < 2\pi0αi<2π)。
  3. 将所有 αi\alpha_iαi 累加,得到总旋转角度 T=∑αiT = \sum \alpha_iT=αi
  4. 计算圈数 k=T2πk = \frac{T}{2\pi}k=2πT ,并四舍五入到最近的整数(由于浮点数精度问题,需要处理微小误差)。

4. 角度计算方法

u⃗=(ux,uy)\vec{u} = (u_x, u_y)u =(ux,uy)v⃗=(vx,vy)\vec{v} = (v_x, v_y)v =(vx,vy) 。我们可以通过向量叉积和点积来计算旋转角度:

  • 叉积: u⃗×v⃗=uxvy−uyvx\vec{u} \times \vec{v} = u_x v_y - u_y v_xu ×v =uxvyuyvx 。它的符号表示旋转方向:正数表示逆时针旋转,负数表示顺时针旋转。
  • 点积: u⃗⋅v⃗=uxvx+uyvy\vec{u} \cdot \vec{v} = u_x v_x + u_y v_yu v =uxvx+uyvy 。它用于计算两个向量夹角的余弦值。

使用 atan2\texttt{atan2}atan2 函数可以方便地计算两个向量之间的夹角(以弧度为单位):
θ=atan2(u⃗×v⃗, u⃗⋅v⃗) \theta = \texttt{atan2}(\vec{u} \times \vec{v}, \ \vec{u} \cdot \vec{v}) θ=atan2(u ×v , u v )
atan2\texttt{atan2}atan2 返回值的范围是 [−π,π][-\pi, \pi][π,π] 。由于我们需要的是逆时针旋转的角度(0002π2\pi2π),因此当 θ<0\theta < 0θ<0 时,需要加上 2π2\pi2π

5. 算法步骤

  1. 读入点的数量 NNN ,如果 N=0N = 0N=0 则结束程序。
  2. 读入 NNN 个点的坐标,存储到数组中。
  3. 为了方便处理循环,将第一个点复制到数组末尾,这样 PN=P0P_{N} = P_0PN=P0
  4. 对于每个顶点 PiP_iPii=0i = 0i=0N−1N-1N1):
    • 计算向量 u⃗=Pi−1Pi\vec{u} = P_{i-1}P_iu =Pi1Piv⃗=PiPi+1\vec{v} = P_i P_{i+1}v =PiPi+1 。注意当 i=0i = 0i=0 时, Pi−1P_{i-1}Pi1 应该是 PN−1P_{N-1}PN1 (即原始多边形的最后一个点)。
    • 使用上述方法计算从 u⃗\vec{u}u v⃗\vec{v}v 的逆时针旋转角度 αi\alpha_iαi
    • 累加 αi\alpha_iαi
  5. 计算总旋转角度 TTT ,然后计算圈数 k=round(T/(2π))k = \texttt{round}(T / (2\pi))k=round(T/(2π))
  6. 输出 kkk

6. 时间复杂度

该算法只需遍历所有顶点一次,每次计算都是常数时间,因此时间复杂度为 O(N)O(N)O(N) ,空间复杂度为 O(N)O(N)O(N) 。由于 N<1000N < 1000N<1000 ,该算法效率很高。

7. 边界情况与精度处理

  • 浮点数精度:由于使用 atan2\texttt{atan2}atan2 和除法,结果可能会有微小误差。因此,在计算圈数时,我们使用 round\texttt{round}round 函数进行四舍五入,而不是直接截断。
  • 坐标范围:坐标值的绝对值可达 10610^6106 ,在计算叉积和点积时,可能会超过 323232 位整数的范围。因此,我们使用 double 类型进行计算,避免溢出。

代码实现

// Touring Robot
// UVa ID: 11509
// Verdict: Accepted
// Submission Date: 2026-01-07
// UVa Run Time: 0.010s
//
// 版权所有(C)2026,邱秋。metaphysis # yeah dot net

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

const double PI = acos(-1.0);
const double EPS = 1e-9;

struct Point {
    int x, y;
    Point(int x = 0, int y = 0) : x(x), y(y) {}
};

// 计算两点之间的向量
Point vectorBetween(const Point& a, const Point& b) {
    return Point(b.x - a.x, b.y - a.y);
}

// 计算从向量u旋转到向量v的逆时针角度(0到2π)
double rotationAngle(const Point& u, const Point& v) {
    double cross = u.x * v.y - u.y * v.x;  // 叉积
    double dot = u.x * v.x + u.y * v.y;    // 点积
    // 使用atan2计算角度,确保结果在[0, 2π)范围内
    double angle = atan2(cross, dot);
    if (angle < 0) angle += 2 * PI;
    return angle;
}

int main() {
    int n;
    while (scanf("%d", &n) == 1 && n != 0) {
        vector<Point> points(n);
        for (int i = 0; i < n; i++) scanf("%d %d", &points[i].x, &points[i].y);
        // 多边形闭合,添加第一个点作为最后一个点以便计算
        points.push_back(points[0]);
        double totalAngle = 0.0;
        for (int i = 0; i < n; i++) {
            // 当前点
            Point current = points[i];
            // 前一个点
            Point prev = (i == 0) ? points[n - 1] : points[i - 1];
            // 下一个点
            Point next = points[i + 1];
            // 计算两个向量:从前一点到当前点,从当前点到下一点
            Point vec1 = vectorBetween(prev, current);
            Point vec2 = vectorBetween(current, next);
            // 累加旋转角度
            totalAngle += rotationAngle(vec1, vec2);
        }
        // 总旋转角度除以2π得到圈数,四舍五入到最接近的整数
        int turns = round(totalAngle / (2 * PI));
        printf("%d\n", turns);
    }
    return 0;
}

总结

本题通过将机器人的运动路径抽象为闭合多边形,并将旋转圈数问题转化为计算多边形旋转数的几何问题,从而得到了一个简洁高效的解法。关键在于理解外角之和与旋转数之间的关系,并正确使用向量运算计算每个顶点的旋转角度。算法的时间复杂度为 O(N)O(N)O(N) ,完全满足题目要求。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐