题解:AtCoder AT_awc0125_c Gear Synchronization

【题目来源】

AtCoder:C - Gear Synchronization

【题目描述】

高橋组装了一个由 \(N\) 个齿轮排成一排的装置。齿轮按顺序编号为 \(1\)\(N\),相邻的齿轮 \(i\)\(i+1\)\(1 \leq i \leq N-1\))相互啮合。

每个齿轮 \(i\)\(1 \leq i \leq N\))有 \(T_i\) 个齿。当齿轮 \(i\) 和齿轮 \(i+1\) 啮合时,如果一个旋转,另一个也会相应旋转。由于两个啮合的齿轮前进的齿数相同,当齿轮 \(i\) 旋转一整圈(\(T_i\) 个齿)时,齿轮 \(i+1\) 旋转 \(\frac{T_i}{T_{i+1}}\) 圈。换句话说,齿轮 \(i\) 与齿轮 \(i+1\) 的旋转次数之比为 \(T_{i+1} : T_i\)(齿数更多的齿轮旋转得更慢)。

每个齿轮恰好有一个标记齿。当齿轮恰好旋转正整数圈时,所有齿都回到原来的位置,因此标记齿也回到原来的位置。反之,如果旋转次数不是正整数,标记就不会回到原来的位置。注意,相邻齿轮的旋转方向相反,但标记是否回到原来的位置仅取决于旋转次数,与旋转方向无关。

当齿轮 \(1\) 旋转时,齿轮 \(2, 3, \ldots, N\) 也会通过啮合相应旋转。设齿轮 \(1\) 的旋转次数为 \(R\)\(R > 0\))。求最小的正实数 \(R\),使得所有齿轮上的标记同时回到各自原来的位置。这里,旋转次数是一个正实数,表示齿轮旋转了多少圈,与旋转方向无关。

可以证明 \(R\) 总是一个正有理数。将 \(R\) 表示为最简分数 \(\frac{P}{Q}\)\(P, Q\) 为正整数,\(\gcd(P, Q) = 1\)),输出 \(P\)\(Q\) 并用 / 分隔。如果 \(R\) 是整数,按 P/1 的格式输出。

【输入】

\(N\)
\(T_1\) \(T_2\) \(\ldots\) \(T_N\)

  • The first line contains an integer \(N\) representing the number of gears.
  • The second line contains \(N\) integers \(T_1, T_2, \ldots, T_N\) separated by spaces, representing the number of teeth on each gear.

【输出】

Output the numerator \(P\) and denominator \(Q\) of the irreducible fraction representing the smallest positive number of rotations of gear \(1\) such that the marks on all gears simultaneously return to their respective original positions, separated by / on a single line. If the answer is an integer, output it in the format P/1.

【输入样例】

3
8 12 6

【输出样例】

3/1

【核心思想】

  1. 问题分析:给定 \(N\) 个齿轮排成一排,齿轮 \(i\)\(T_i\) 个齿,相邻齿轮啮合。齿轮 \(1\) 旋转 \(R\) 圈时,齿轮 \(i\) 旋转 \(R \cdot \frac{T_1}{T_i}\) 圈。要求找到最小的正实数 \(R\),使得所有齿轮的标记同时回到原位。标记回到原位当且仅当旋转圈数为正整数。

  2. 算法选择

    • 数学推导:齿轮 \(i\) 的旋转圈数为 \(R \cdot \frac{T_1}{T_i}\),要使其为正整数,需 \(R \cdot T_1\)\(T_i\) 的倍数
    • 最小公倍数(LCM)\(R \cdot T_1\) 需为所有 \(T_i\) 的公倍数,最小取 \(\text{lcm}(T_1, T_2, \ldots, T_N)\)
    • 欧几里得算法:通过 \(\text{lcm}(a, b) = \frac{a \cdot b}{\gcd(a, b)}\) 递推计算多个数的最小公倍数
    • 大整数处理:使用 __int128 防止中间乘积溢出
  3. 关键步骤

    • 读入数据\(N\) 和数组 \(T[1..N]\)
    • 递推计算 LCM
      • 初始化 \(\text{lcm\_val} = T_1\)
      • 遍历 \(i\)\(1\)\(N\)
        • \(g = \gcd(\text{lcm\_val}, T_i)\)
        • \(\text{lcm\_val} = \frac{\text{lcm\_val}}{g} \cdot T_i\)(先除后乘防溢出)
    • 计算答案\(R = \frac{\text{lcm\_val}}{T_1}\),由于 \(\text{lcm\_val}\)\(T_1\) 的倍数,结果必为整数
    • 输出:以 P/1 格式输出
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \cdot \log(\max T_i))\),每次 \(\gcd\) 计算为对数级
    • 空间复杂度:\(O(N)\),存储齿数数组
  5. 数论推导的核心思想

    • 传动比链式推导:齿轮 \(i\) 与齿轮 \(i+1\) 的转速比为 \(T_{i+1} : T_i\),齿轮 \(1\) 到齿轮 \(i\) 的总传动比为 \(\frac{T_1}{T_i}\)
    • 整除条件转化\(R \cdot \frac{T_1}{T_i} \in \mathbb{Z}^+\) 等价于 \(T_i \mid R \cdot T_1\),即 \(R \cdot T_1\) 是所有 \(T_i\) 的公倍数
    • LCM 最小性:最小公倍数保证了 \(R\) 的最小性,任何更小的 \(R\) 都会导致至少一个齿轮旋转非整数圈
    • 防溢出技巧:计算 \(\text{lcm}\) 时先除后乘(\(\frac{a}{\gcd(a,b)} \cdot b\)),避免 \(a \cdot b\) 直接溢出
    • 大整数必要性\(N\) 个数的 LCM 可能极大,需 __int128 支持
    • 适用于齿轮传动、周期同步、最小公倍数类问题

【算法标签】

欧几里得算法

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int __int128      // 将int定义为__int128,处理大整数运算(T_i乘积可能极大)
const int N = 200005;     // 定义数组最大容量为200005
int n;                    // n为齿轮数量
int a[N];                 // a[i]表示第i个齿轮的齿数T_i

// 自定义128位整数读取函数(因为__int128不支持标准输入)
int read()
{
    int x = 0, f = 1;     // x为结果,f为符号标志(1为正,-1为负)
    char ch = getchar();  // 读取第一个字符

    // 跳过空白字符,处理可能的负号
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
        {
            f = -1;       // 标记为负数
        }
        ch = getchar();   // 继续读取下一个字符
    }

    // 读取连续的数字字符,构建整数
    while (ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - 48;  // 字符'0'-'9'转换为数字0-9,累加到结果中
        ch = getchar();        // 读取下一个字符
    }

    return x * f;             // 返回最终结果(考虑符号)
}

// 自定义128位整数输出函数(递归实现)
void write(int x)
{
    if (x < 0)               // 处理负数情况
    {
        putchar('-');        // 输出负号
        x = -x;              // 转为正数处理
    }

    if (x > 9)               // 如果x大于9,说明还有高位数字
    {
        write(x / 10);       // 递归输出高位数字
    }

    putchar(x % 10 + '0');   // 输出当前最低位数字
    return;
}

// 最大公约数函数(辗转相除法)
int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;  // 如果b不为0,递归计算gcd(b, a%b);否则返回a
}

signed main()                // 使用signed main配合#define int __int128
{
    n = read();                // 读入齿轮数量n
    for (int i = 1; i <= n; i++)
        a[i] = read();         // 读入每个齿轮的齿数

    // 核心推导:
    // 齿轮1旋转R圈时,齿轮i旋转的圈数为 R * (T_1/T_i)
    // 要使所有齿轮的标记回到原位,需要 R * (T_1/T_i) 为正整数,对所有i成立
    // 即 R * T_1 是 T_i 的倍数,对所有i成立
    // 最小R = lcm(T_1, T_2, ..., T_N) / T_1

    int lcm_val = a[1];        // 初始化lcm为T_1
    for (int i = 1; i <= n; i++)  // 计算T_1到T_N的最小公倍数
    {
        int g = gcd(lcm_val, a[i]);  // 计算当前lcm与a[i]的最大公约数
        lcm_val = lcm_val / g * a[i];  // 更新lcm = lcm * a[i] / gcd(防止溢出,先除后乘)
    }

    // 输出结果:R = lcm(T_1,...,T_N) / T_1,分母为1(因为lcm一定是T_1的倍数)
    write(lcm_val / a[1]);     // 输出分子(lcm除以T_1)
    cout << "/1" << endl;      // 输出分母1(结果一定是整数)

    return 0;
}

【运行结果】

3
8 12 6
3/1
posted @ 2026-08-05 16:22  团爸讲算法  阅读(13)  评论(0)    收藏  举报