题解:洛谷 P1467 循环数Runaround Numbers

【题目来源】

洛谷:P1467 [USACO2.2] 循环数 Runaround Numbers - 洛谷

【题目描述】

循环数是那些不包括 \(0\) 且没有重复数字的正整数(比如 \(81362\)),并且还应同时具有一个有趣的性质——

如果你从最左边的数字开始(在 \(\color{red}{8}\color{black}1362\) 中是 \(8\))向右数这个数字对应的次数(如果数到了最右边就回到最左边;在 \(\color{red}{8}\color{black}1362\) 中是 \(8\) 次),你会停止在另一个新的数字(在 \(\color{red}{8}\color{black}1362\) 中是 \(\color{red}8\color{black}\to 1\to 3\to 6\to 2\to 8\to 1\to 3\to \color{red}6\);如果停在一个相同的数字上,这个数就不是循环数)。

重复这样做,如果能在经过每个数位恰好一次后回到最左边,那么这个正整数就是循环数。

仍然以 \(81362\) 为例,以下模拟过程证明了 \(81362\) 是循环数:

\[\color{red}8\color{black}\to 1\to 3\to 6\to 2\to 8\to 1\to 3\to \color{red}6 \]

\[\color{red}6\color{black}\to 2\to 8\to 1\to 3\to 6\to \color{red}2 \]

\[\color{red}2\color{black}\to 8\to \color{red}1 \]

\[\color{red}1\color{black}\to \color{red}3 \]

\[\color{red}3\color{black}\to 6\to 2\to \color{red}8 \]

任务:

给你一个正整数 \(m\),找出最小的比 \(m\) 大的循环数 \(m'\)

数据保证 \(m' \le 2^{32}-1\)

【输入】

仅仅一行, 包括 \(m\)

【输出】

仅仅一行,输出最小的比 \(m\) 大的循环数 \(m'\)

【输入样例】

81361

【输出样例】

81362

【核心思想】

  1. 问题分析:给定正整数 \(m\),要求找到最小的比 \(m\) 大的循环数。循环数的定义:不含 \(0\)、无重复数字的正整数,从最高位开始,按当前位数字值向右循环移动(到末尾回到开头),经过每个数位恰好一次后回到起始位置。

  2. 算法选择

    • 暴力枚举 + 模拟验证:从 \(m+1\) 开始逐个枚举,对每个数模拟循环数判定过程
    • 循环数判定函数:包含四个检查条件——不含 \(0\)、无重复数字、模拟移动经过所有位、最终回到起始位
  3. 关键步骤

    • 读入\(m\)
    • 枚举\(i\)\(m+1\) 开始递增
    • 判定函数 find(n)
      • 条件 1(不含 0):将 \(n\) 转为字符串,若含 '0' 返回 false
      • 条件 2(无重复数字):用桶数组 b[1..9] 统计各数字出现次数,若存在 \(>1\) 返回 false
      • 条件 3(模拟移动)
        • 初始化 mark = 0(起始位置),a 数组标记访问状态
        • 循环 \(len\) 次(\(len\) 为数字位数):
          • a[mark] = 1 标记当前位已访问
          • mark = (mark + (s[mark] - '0')) % len 计算下一个位置
        • 循环结束后检查:s[mark] == s[0](回到起始位的数字)
        • 检查 a 数组是否全为 \(1\)(所有位均被访问恰好一次)
      • 全部满足返回 true
    • 输出:第一个满足 find(i)\(i\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O((m' - m) \cdot d)\)\(d\) 为数字位数,实际循环数密度较高,枚举量不大
    • 空间复杂度:\(O(d)\),字符串和标记数组
  5. 模拟验证的核心思想

    • 循环移动建模:用模运算 (mark + digit) % len 实现"到末尾回到开头"的循环效果
    • 访问完整性检查a 数组确保每个位置被恰好访问一次,防止提前进入小循环
    • 回到起点验证:循环 \(len\) 次后必须停在起始位的数字上,保证路径闭合
    • 数字约束前置:先排除含 \(0\) 和重复数字的数,减少无效模拟
    • 适用于数字特性模拟、循环路径验证、暴力搜索类问题

【解题思路】

【算法标签】

普及- #模拟

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int m, a[35], b[15];  // 定义a数组用来存放每个数字的遍历,定义b数组用来存放每个数字出现的次数
bool find(int n) {
    memset(a, 0, sizeof(a));  // 初始化a数组
    memset(b, 0, sizeof(b));  // 初始化b数组
    string s = to_string(n);  // 将n转为字符串s
    for (int i=0; i<s.length(); i++) {  // 遍历s字符串
        if (s[i]=='0') return false;  // 如果其中有字符'0',则返回false
    }
    for (int i=0; i<s.length(); i++) {  // 遍历s字符串
        b[(s[i]-'0')]++;  // 使用桶记录每个数字出现的次数
    }
    for (int i=1; i<=9; i++) {  // 在1-9中(没有0,是因为有0就已经退出循环了)
        if (b[i]>1) return false;  // 如果某个数字的计数大于1,说明有重复数字,返回false
    }
    int mark=0;  // 其实下标为0
    for (int i=0; i<s.length(); i++) {  // 循环s字符串长度的次数
        a[mark] = 1;  // 将a数组中对应下标修改为1,表示此下标已经遍历过
        mark = (mark+(s[mark]-'0'))%s.length();  // 下标加上下标对应的数字的和,对长度取余,得到新的下标
    }
    if (s[mark]!=s[0]) return false;  // 循环完后,更新后的mark下标对应的数字如果和开始字符,即下标为0的字符相同,则符合要求,否则返回false
    for (int i=0; i<s.length(); i++) {  // 遍历a数组
        if (a[i]==0) return false;  // 如果有位置还为0,说明对应下标的数字没有被遍历到,返回false
    }
    return true;  // 如果以上条件否可以满足,则返回true
}
int main()
{
    cin >> m;  // 输入m
    for (int i=m+1; i<=1e9; i++) {  // 从m+1开始遍历,最大数字为1e9
        if (find(i)) {  // 判断i是否符合要求
            cout << i << endl;  // 如果如何要求则输出
            break;  // 并退出循环
        }
    }
    return 0;
}

【运行结果】

81361
81362
posted @ 2026-08-03 08:47  团爸讲算法  阅读(11)  评论(0)    收藏  举报