题解:洛谷 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\) 是循环数:
任务:
给你一个正整数 \(m\),找出最小的比 \(m\) 大的循环数 \(m'\)。
数据保证 \(m' \le 2^{32}-1\)。
【输入】
仅仅一行, 包括 \(m\)。
【输出】
仅仅一行,输出最小的比 \(m\) 大的循环数 \(m'\)。
【输入样例】
81361
【输出样例】
81362
【核心思想】
-
问题分析:给定正整数 \(m\),要求找到最小的比 \(m\) 大的循环数。循环数的定义:不含 \(0\)、无重复数字的正整数,从最高位开始,按当前位数字值向右循环移动(到末尾回到开头),经过每个数位恰好一次后回到起始位置。
-
算法选择:
- 暴力枚举 + 模拟验证:从 \(m+1\) 开始逐个枚举,对每个数模拟循环数判定过程
- 循环数判定函数:包含四个检查条件——不含 \(0\)、无重复数字、模拟移动经过所有位、最终回到起始位
-
关键步骤:
- 读入:\(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
- 条件 1(不含 0):将 \(n\) 转为字符串,若含
- 输出:第一个满足
find(i)的 \(i\)
-
时间/空间复杂度:
- 时间复杂度:\(O((m' - m) \cdot d)\),\(d\) 为数字位数,实际循环数密度较高,枚举量不大
- 空间复杂度:\(O(d)\),字符串和标记数组
-
模拟验证的核心思想:
- 循环移动建模:用模运算
(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
浙公网安备 33010602011771号