UVa 263 Number Chains
题目分析
本题要求我们对给定的正整数进行一种特定的数字变换,并记录变换过程中形成的“数字链”,直到出现重复的数字为止。变换规则如下:
- 将当前数字的各位数字按降序排列,得到一个大数 bigbigbig。
- 将当前数字的各位数字按升序排列,得到一个小数 smallsmallsmall。
- 计算差值 difference=big−smalldifference = big - smalldifference=big−small。
- 用差值作为新的数字,重复上述步骤,直到新生成的数字已经在之前的链中出现过为止。
需要注意:
- 数字允许包含前导零(例如 123412341234 的升序排列是 123412341234,降序是 432143214321;而 308730873087 的升序是 0378=3780378 = 3780378=378,降序是 873087308730)。
- 输入数字小于 10910^9109,最多有 500050005000 个数字。
- 每条链的长度定义为链中不同数字的个数。
- 输出格式要求:每个数字链的输出后跟一个空行。
解题思路
1. 问题本质
这个问题的本质是模拟一个确定的数字变换过程,并检测循环。由于每一步变换的结果完全由当前数字的各位数字决定,因此这是一个确定性系统。由于数字范围有限(小于 10910^9109,即最多 999 位),可能的数字状态是有限的,因此必然会进入循环。
2. 算法设计
我们需要对每个输入数字:
- 输出原始数字(格式要求第一行)。
- 使用一个集合(
set)记录已经出现过的数字。 - 循环进行变换:
- 将当前数字转换为字符串,以便对各位数字进行排序。
- 分别按降序和升序排序得到 bigbigbig 和 smallsmallsmall。
- 计算差值。
- 输出当前步骤的算式。
- 检查差值是否已经在集合中:
- 如果已存在,说明链结束,输出链长度。
- 否则,将差值加入集合,更新当前数字,链长度加 111。
3. 细节处理
3.1 数字与字符串的转换
在 C++ 中,可以使用 to_string 将整数转换为字符串,使用 stoi 将字符串转换为整数。
3.2 排序规则
- 降序排列:按字符从大到小排序,对应较大的数字。
- 升序排列:按字符从小到大排序,对应较小的数字。
注意:升序排列后可能产生前导零(例如 100100100 的升序是 001=1001 = 1001=1),但 stoi 会自动忽略前导零,因此无需特殊处理。
3.3 链的长度统计
题目要求链的长度是链中不同数字的个数。在我们的算法中,当新生成的差值已经在集合中出现时,我们不将它再次加入集合,此时链的长度 lengthlengthlength 已经记录了集合中元素的个数。
3.4 循环终止条件
循环在发现新生成的差值已经在集合中时终止。例如 444444444 的情况:
- 原始数字 444444444,降序 444444444,升序 444444444,差值为 000。
- 000 未出现过,继续。
- 下一轮:000 的降序和升序都是 000,差值为 000,此时 000 已经在集合中,终止。
这样链的长度为 222,与示例输出一致。
4. 复杂度分析
- 每个数字最多变换 100010001000 次(题目保证)。
- 每次变换需要排序,数字位数最多 999,排序复杂度可视为 O(9log9)≈O(1)O(9 \log 9) \approx O(1)O(9log9)≈O(1)。
- 集合的插入和查找操作平均 O(logL)O(\log L)O(logL),其中 LLL 是当前链长度,L≤1000L \leq 1000L≤1000。
- 总时间复杂度:对于每个输入数字,O(1000log1000)≈O(104)O(1000 \log 1000) \approx O(10^4)O(1000log1000)≈O(104),对于 500050005000 个数字完全可行。
代码实现
// Number Chains
// UVa ID: 263
// Verdict: Accepted
// Submission Date: 2016-05-10
// UVa Run Time: 0.380s
//
// 版权所有(C)2016,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
// 降序比较函数,用于将数字的各位从大到小排列
bool cmp1(char x, char y) {
return x > y;
}
// 升序比较函数,用于将数字的各位从小到大排列
bool cmp2(char x, char y) {
return x < y;
}
int main() {
cin.tie(0); cin.sync_with_stdio(false);
int number;
// 持续读入数字,直到遇到 0 为止
while (cin >> number, number) {
// 输出原始数字,格式要求第一行
cout << "Original number was " << number << endl;
int length = 1; // 链的长度,初始包含原始数字
set<int> appeared; // 记录已经出现过的数字
while (true) {
// 将当前数字转为字符串,以便对各位数字排序
string text = to_string(number);
// 降序排列得到大数
sort(text.begin(), text.end(), cmp1);
int big = stoi(text);
// 升序排列得到小数
sort(text.begin(), text.end(), cmp2);
int small = stoi(text);
// 计算差值
int difference = big - small;
// 输出当前步骤的算式
cout << big << " - " << small << " = " << difference << endl;
// 如果差值已经在链中出现过,则链结束
if (appeared.count(difference) > 0) {
cout << "Chain length " << length << endl;
break;
} else appeared.insert(difference); // 将新差值加入集合
number = difference; // 用差值继续下一轮变换
length++; // 链长度增加
}
// 每个数字链输出后跟一个空行
cout << endl;
}
return 0;
}
总结
本题是一个简单的模拟题,核心在于正确实现数字各位的排序与差值计算,并利用集合检测循环。代码结构清晰,使用 set 记录历史数字,避免了复杂的循环检测逻辑。注意输出格式中的空行要求,以及链长度的正确计算方式。通过本题可以熟悉 C++ 中字符串与整数的转换、排序函数以及集合的使用。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)