usaco-2.2.3 循环数

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

从 M+1 起线性搜索,用位运算 O(L) 判定循环数:各位非零互异,且按下标跳格恰好走遍所有位回到起点。

OJ: roj

题目 ID: 2028

难度:普及-

标签:模拟枚举

创建: 2026-10-01 04:05

更新: 2026-10-06 09:51

题目描述

求严格大于 MM 的最小循环数。循环数要求十进制各位不含 00 且两两不同;从下标 00 开始,每步按当前位数字向右跳格并回卷,恰好走遍所有位后回到起点。

思路

从 M+1M+1 开始逐个整数枚举,先快速排除含 00 或数字重复的情况,再 O(L)O(L) 模拟跳格判是否走遍所有位;循环数最大为 96824159682415,超过即不存在。

参考代码

Python 版:

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-01 04:20
# update_at: 2026-10-01 04:20

import sys

MAX_RUNAROUND = 9682415  # 位数递增时循环数总数为 9+20+12+48+160+96+112,7 位以上为空


def is_runaround(n: int) -> bool:
    """n 的十进制各位非零且互不相同,且从第 0 位起按"当前位数字"跳格恰好走遍所有位并回到 0。"""
    s = str(n)
    if '0' in s or len(set(s)) != len(s):  # 含 0 或数字重复,直接排除
        return False
    length = len(s)
    pos, visited = 0, 0
    for _ in range(length):
        if visited >> pos & 1:  # 同一位置踩两次:说明跳格没覆盖全部位,绕不到起点
            return False
        visited |= 1 << pos
        pos = (pos + int(s[pos])) % length  # 向右数 s[pos] 格,越界则回卷
    return pos == 0  # 恰好走完 length 步后回到起点


def solve() -> None:
    m = int(sys.stdin.buffer.read())
    n = m + 1
    while n <= MAX_RUNAROUND and not is_runaround(n):  # 超过上界就不存在更大的循环数
        n += 1
    print(n)


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

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-06 09:51
 * update_at: 2026-10-06 09:51
 */

#include <cstdio>
#include <cstring>
using namespace std;

typedef long long ll;

const ll MAX_RUNAROUND = 9682415; // 循环数最大为 9682415,7 位以上不存在

// 判断 n 是否为循环数:各位非零互异,且从第 0 位起按当前位数字跳格走遍所有位并回到 0
bool is_runaround(ll n) {
    char s[32];
    snprintf(s, sizeof(s), "%lld", n);
    int len = strlen(s);

    bool seen[10] = {false};
    for (int i = 0; i < len; i++) {
        int d = s[i] - '0';
        if (d == 0) return false;       // 含 0 直接排除
        if (seen[d]) return false;      // 数字重复直接排除
        seen[d] = true;
    }

    int pos = 0;
    int visited = 0; // 用 bitmask 记录已访问的下标
    for (int i = 0; i < len; i++) {
        if (visited >> pos & 1) return false; // 重复踩点,未走遍全部位
        visited |= 1 << pos;
        pos = (pos + (s[pos] - '0')) % len;
    }
    return pos == 0;
}

int main() {
    ll m;
    scanf("%lld", &m);

    ll n = m + 1;
    // 线性搜索第一个大于 m 的循环数,超过上界则停止
    while (n <= MAX_RUNAROUND && !is_runaround(n)) n++;

    printf("%lld\n", n);
    return 0;
}