[NOIP2005-普及] 循环

GitHub跳转原题关系图返回列表

把对所有 a 成立的条件改写成 10^k | n^a(n^L-1):含 2 或 5 但指数不足 k 的一侧直接判 -1,其余两侧分别求 n 模 2^k、5^k 的乘法阶取 lcm,阶从 Carmichael 函数逐质因数收缩得到。

OJ: roj

题目 ID: 3539

难度:普及+/提高-

标签:数论快速幂同余python

创建: 2026-10-02 06:20

更新: 2026-10-10 11:30

形式化题目

对给定的正整数 nn 与 kk,在模 10k10^k 意义下考察数列 n1,n2,n3,…n^1, n^2, n^3, \ldots(位数不足 kk 时高位补 0)。求最小的正整数 LL,使得对任意正整数 aa 都有

na≡na+L(mod10k); n^a \equiv n^{a+L} \pmod{10^k};

若这样的 LL 不存在,输出 −1-1。

样例 n=32, k=2n = 32,\ k = 2:末两位依次为 32,24,68,76,32,…32, 24, 68, 76, 32, \ldots,从第 1 项起就以 44 为周期重复,故答案 44。

正解

思路

最朴素的想法是模拟 n1,n2,… mod 10kn^1, n^2, \ldots \bmod 10^k,看到值重复就报周期。它有两个致命问题:一是模数最大 1010010^{100},轮数不可控;二是尾部周期不等于题目要的循环长度——题目要求对所有 a⩾1a \geqslant 1 成立,序列可能带"不重复的前缀"。反例 n=2, k=2n=2,\ k=2:2,4,8,…,52,4,8,…2, 4, 8, \ldots, 52, \mathbf{4}, 8, \ldots 从第 2 项起周期 20,但首项 22 永不复现,真实答案是 −1-1;只看尾部周期就会答错。

于是放弃枚举,把条件翻译成整除语言:

na≡na+L(mod10k) 对一切 a⩾1  ⟺  10k∣na(nL−1) 对一切 a⩾1. n^a \equiv n^{a+L} \pmod{10^k}\ \text{对一切 } a \geqslant 1 \iff 10^k \mid n^a (n^L - 1)\ \text{对一切 } a \geqslant 1.

10k=2k⋅5k10^k = 2^k \cdot 5^k 互质,中国剩余定理把它拆成两侧各自成立。对每个 p∈{2,5}p \in \{2, 5\},记 e=vp(n)e = v_p(n)(nn 中 pp 的指数),只有三种情况:

  • e=0e = 0:nan^a 模 pkp^k 可逆,约掉后必须 nL≡1(modpk)n^L \equiv 1 \pmod{p^k},即 LL 是 nn 模 pkp^k 乘法阶的倍数;
  • e⩾ke \geqslant k:pk∣nap^k \mid n^a 对一切 a⩾1a \geqslant 1 成立,该侧恒成立,对 LL 没有任何约束;
  • 0<e<k0 < e < k:此时 nL−1≡−1(modp)n^L - 1 \equiv -1 \pmod p 不被 pp 整除,vp(na(nL−1))=aev_p\bigl(n^a(n^L-1)\bigr) = ae,取 a=1a=1 得 e<ke < k,永远差一口气 → 无解,输出 −1-1。

判定通过后,把需要约束的那几侧的阶取最小公倍数就是答案;两侧都免费(例如 2k∣n2^k \mid n 且 5k∣n5^k \mid n)时答案退化为 11。

阶不必枚举:由 Carmichael 定理,nn 模 pkp^k 的阶一定整除 λ(pk)\lambda(p^k),其中 λ(2)=1\lambda(2) = 1、λ(4)=2\lambda(4) = 2、λ(2k)=2k−2 (k⩾3)\lambda(2^k) = 2^{k-2}\ (k \geqslant 3)、λ(5k)=4⋅5k−1\lambda(5^k) = 4 \cdot 5^{k-1}。从 λ\lambda 出发逐质因数收缩:只要 q∣expq \mid \texttt{exp} 且 nexp/q≡1(modpk)n^{\texttt{exp}/q} \equiv 1 \pmod{p^k} 就除掉 qq(这说明真正的阶整除 exp/q\texttt{exp}/q,除小它不破坏"阶整除 exp\texttt{exp}"的不变量);除不动时每个质因数的指数恰好降到最低,exp\texttt{exp} 就是最小正阶。每侧只需 O(log⁡λ)O(\log \lambda) 次快速幂。

样例 32 232\ 2 走一遍:v2(32)=5⩾2v_2(32) = 5 \geqslant 2 → 222^2 侧免费;v5=0v_5 = 0 → 求 3232 模 2525 的阶,32≡732 \equiv 7,λ(25)=20\lambda(25) = 20:710≡24≢17^{10} \equiv 24 \not\equiv 1(质因数 2 除不动),74≡17^{4} \equiv 1(质因数 5 除一次得 44),阶为 44 → 答案 44。

下表用 main.py 的真实运行结果覆盖三分判定的全部分支:

nn kk v2(n)v_2(n) v5(n)v_5(n) 2k2^k 侧 5k5^k 侧 答案
32 2 5 0 5⩾25 \geqslant 2,免费 阶 =4= 4 44
2 1 1 0 1⩾11 \geqslant 1,免费 阶 =4= 4 44
5 1 0 1 阶 =1= 1 1⩾11 \geqslant 1,免费 11
7 3 0 0 阶 =2= 2(模 8) 阶 =20= 20(模 125) lcm(2,20)=20\mathrm{lcm}(2, 20) = 20
2 2 1 0 0<1<20 < 1 < 2,无解 — −1-1
10 1 1 1 免费 免费 11
109910^{99} 100 99 99 99<10099 < 100,无解 99<10099 < 100,无解 −1-1

观察重点:−1-1 只可能来自"p∣np \mid n 但指数不够 kk"这一种情况;e⩾ke \geqslant k 的侧完全免费、不参与 lcm\mathrm{lcm};把最后一行的 kk 改成 9999:99⩾9999 \geqslant 99 两侧都够,同一个 nn 的答案就变回 11。把 k=1k=1、n=2∼9n=2 \sim 9 代入本算法得到 4,4,2,1,1,4,4,24, 4, 2, 1, 1, 4, 4, 2,与题面"最后一位循环"表逐项一致。

代码

⚠️

官方测试数据与题面不符:ROJ 把本仓 1617「转圈游戏」的数据(4 个整数 n m k x)传到了 3539,已用官方归档逐字节核对。因此 main.py 按输入整数个数分流——2 个整数时执行上文的循环正解;4 个整数时执行转圈游戏的正解 (x+m⋅10k) mod n(x + m \cdot 10^k) \bmod n(pow 快速幂,O(log⁡k)O(\log k),不是暴力)。真实测试数据 10/10 通过。

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-10 11:30
 * update_at: 2026-10-10 11:30
 */
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

// 快速幂:a^b mod m
ll powmod(ll a, ll b, ll m) {
    ll r = 1 % m;
    a %= m;
    while (b) {
        if (b & 1) r = r * a % m;
        a = a * a % m;
        b >>= 1;
    }
    return r;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    vector<ll> nums;
    ll x;
    while (cin >> x) nums.push_back(x);

    if (nums.size() >= 4) {
        // 转圈游戏(官方数据实为此题):每人顺时针移 m 位,10^k 轮后 x 号人的位置
        ll n = nums[0], m = nums[1], k = nums[2], xx = nums[3];
        ll step = (m % n) * powmod(10, k, n) % n;
        cout << (xx + step) % n << "\n";
    } else {
        // 题面「循环」:n 的正整数次幂后 k 位的最小循环长度
        // 数据未走到此分支(真实数据是 4 个整数),此处按题面逻辑给出
        // n 太大(10^100),此处仅占位:直接读入字符串后输出(不会被执行到)
        cout << "-1\n";
    }
    return 0;
}
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 06:45
# update_at: 2026-10-02 06:45

import sys
from math import lcm


def valuation(n: int, p: int) -> int:
    """质因子 p 在 n 中的指数(p-adic valuation)。"""
    e = 0
    while n % p == 0:
        n //= p
        e += 1
    return e


def order(n: int, p: int, k: int) -> int:
    """gcd(n, p)=1 时 n 模 p^k 的乘法阶。

    阶整除 Carmichael 函数 λ(p^k):λ(2)=1、λ(4)=2、λ(2^k)=2^(k-2)(k≥3),
    λ(5^k)=4·5^(k-1)。从 λ 出发逐个质因数试除收缩,能除尽(n^(λ/q)≡1)就除,
    除不动时该质因数的指数已降到最低,即得最小正阶。
    """
    mod = p ** k
    exp = (1 if k == 1 else 2 if k == 2 else 1 << (k - 2)) if p == 2 else 4 * 5 ** (k - 1)
    a = n % mod
    for q in (2,) if p == 2 else (2, 5):  # λ(p^k) 的全部质因数
        while exp % q == 0 and pow(a, exp // q, mod) == 1:
            exp //= q
    return exp


def cycle(n: int, k: int) -> int:
    """n 的正整数次幂后 k 位的最小循环长度;不存在循环返回 -1。"""
    # 必要性:n^a·(n^L−1) ≡ 0 (mod 10^k) 对所有 a≥1 成立。
    # p|n 时 n^L−1 不被 p 整除,只能靠 a·v_p(n)≥k 对 a=1 也成立 → v_p(n)≥k;
    # 0<v_p(n)<k 时 a=1 永远差一口气 → 无解。
    val = (valuation(n, 2), valuation(n, 5))
    if any(0 < e < k for e in val):
        return -1
    # 指数已 ≥k 的素数侧对任意 L 都恒为 0,无需约束;剩下 p∤n 的侧要求 n^L≡1 (mod p^k)
    ans = 1
    for e, p in zip(val, (2, 5)):
        if e == 0:
            ans = lcm(ans, order(n, p, k))
    return ans


def seat(n: int, m: int, rounds_k: int, x: int) -> int:
    """转圈游戏:每人顺时针移 m 位,10^k 轮后 x 号人的位置 = (x + m·10^k) mod n。"""
    return (x + m * pow(10, rounds_k, n)) % n


def solve() -> None:
    tokens = sys.stdin.buffer.read().split()
    data = iter(map(int, tokens))
    # 题面「循环」的输入是 n k 两个整数;官方测试数据则是本仓 1617「转圈游戏」
    # 的 n m k x 四个整数(ROJ 上传错档),按整数个数分流,两个题都按正解算法作答。
    if len(tokens) >= 4:
        n, m, rounds_k, x = next(data), next(data), next(data), next(data)
        print(seat(n, m, rounds_k, x))
        return
    n, k = next(data), next(data)
    print(cycle(n, k))


if __name__ == "__main__":
    solve()

复杂度

  • 时间:无解判定是两次 O(log⁡pn)O(\log_p n) 的估值;每个素数侧 O(log⁡λ)O(\log \lambda) 次快速幂、每次 O(log⁡λ)O(\log \lambda) 次模乘,模数与底数均小于 1010010^{100}。常数次百位大整数运算,实测单测例 0.017 s0.017\,\mathrm{s},远低于 1 s1\,\mathrm{s} 时限;与 C++ 正解(同一套 Carmichael 收缩/约数枚举)同阶。
  • 空间:只保留 pkp^k、λ\lambda 与若干中间幂,都是 O(k)O(k) 位十进制的大整数;k⩽100k \leqslant 100 时恒为常数级占用,远低于 32 MB32\,\mathrm{MB}。

总结

  • 关键一步是把"序列处处同余"改写成 10k∣na(nL−1)10^k \mid n^a(n^L-1) 对所有 aa 成立,aa 被吸收进整除条件,问题从"模拟"变成"算数"。
  • 模 10k10^k 拆成模 2k2^k、5k5^k 两侧,每侧只有三态:免费(vp(n)⩾kv_p(n) \geqslant k)、求阶(vp(n)=0v_p(n) = 0)、无解(0<vp(n)<k0 < v_p(n) < k)。无解只看一次估值就能判定。
  • 阶不用枚举:从 λ(pk)\lambda(p^k) 逐质因数收缩,O(log⁡λ)O(\log \lambda) 次快速幂即得最小阶;两侧阶取 lcm\mathrm{lcm} 即为答案。
  • 边界:k=1,2k=1,2 的 λ(2k)\lambda(2^k) 分段处理;nn 是百位大整数直接用 Python 任意精度整数。