P1532 卡布列克圆舞曲【洛谷算法习题】

0 阅读4分钟

P1532 卡布列克圆舞曲

网页链接

P1532 卡布列克圆舞曲 在这里插入图片描述

题目描述

卡布列克是一位数学家,他在研究数字时发现:任意一个不是用完全相同数字组成的四位数,如果对它们的每位数字重新排序,组成一个较大的数和一个较小的数,然后用较大数减去较小数,差不够四位数时补零,类推下去,最后将变成一个固定的数:61746174,这就是卡布列克常数,例如:

43211234=30874321-1234=3087

8730378=83528730-378=8352

85322358=61748532-2358=6174

76411467=61747641-1467=6174

如果 KK 位数也照此办理,它们不是变成一个数,而是在几个数字之间形成循环,称作卡布列克圆舞曲。例如对于五位数 5432154321

5432112345=4197654321-12345=41976

9764114679=8296297641-14679=82962

9862222689=7593398622-22689=75933

9753333579=6395497533-33579=63954

9654334569=6197496543-34569=61974

9764114679=8296297641-14679=82962

我们把 82962,75933,63954,6197482962,75933,63954,61974 称作循环节,即卡布列克圆舞曲。

输入格式

若干行,每行为一个待求“卡布列克圆舞曲”的起始整数 nn。(n<231n<2^{31}

输出格式

每行为对应整数的循环节,数据之间用空格隔开。

输入输出样例 #1

输入 #1

4321
54321

输出 #1

6174
82962 75933 63954 61974

解题思路

本题是数字黑洞(卡布列克常数)的模拟与循环检测问题,通过对整数反复进行“重排最大数减最小数”的操作,记录过程中出现的所有数值,一旦遇到重复即定位循环节并输出。

1. 问题等价转化
  • 卡布列克操作:对于整数 nn,将其各位数字取出,分别按升序和降序排列,组成最小数 n1n_1 和最大数 n2n_2,计算 n=n2n1n' = n_2 - n_1。若结果位数不足原数位数,在运算过程中自然由数字前导零的忽略而实现“补零”效果(例如 30873087 排序后最小数视为 378378,最大数 87308730)。
  • 循环节定义:从某个起点开始,反复执行上述操作,最终会进入一个循环。循环节即从第一次出现的某个数开始,到再次出现该数之前的所有数构成的序列。
  • 目标:对于每个输入整数 nn,输出其进入的循环节(数据之间用空格隔开)。
2. 算法实现
  1. 输入与初始化:循环读取整数 nn(直至 EOF)。对于每个 nn
    • 清空记录数组 a,下标 ind 置 0。
    • 将初始值 nn 存入 a[++ind]
  2. 迭代操作
    • 设置标志 flag = true,进入循环。
    • 提取 nn 的各位数字存入数组 b,记录位数 cnt
    • b[1..cnt] 排序。
    • 构造最小数 n1:从 b[1]b[cnt] 依次拼成十进制数。
    • 构造最大数 n2:从 b[cnt]b[1] 依次拼成十进制数。
    • 计算新数 n = n2 - n1
    • 遍历已记录的数组 a,检查 nn 是否已出现过。若已出现(a[i] == n),则循环节为 a[i]a[ind] 的所有元素,输出后设置 flag = false 结束循环。
    • 否则将 nn 追加到 a[++ind] 中继续迭代。
  3. 输出格式:每找到一组循环节,按顺序输出数字,空格分隔,最后换行。
3. 复杂度分析
  • 时间复杂度:每次操作需要 O(dlogd)O(d \log d) 排序数字(dd 为位数,最多 1010 位),迭代步数受限于整数范围和循环长度,远小于 10510^5 级别,每轮极快。
  • 空间复杂度O(L)O(L) 存储过程值,LL 为循环前序列长度,规模很小。

总结

通过反复执行“重排相减”操作,并将每次结果记录下来,借助线性查找检测重复,精确提取出卡布列克圆舞曲的循环节。由于操作对象不超过 2312^{31},循环长度有限,模拟完全可行。

代码简要说明

  1. 全局变量与数组

    • a[100000]:记录操作过程中依次产生的数。
    • ind:当前记录数量指针。
  2. 主循环

    • while(cin >> n) 处理多组数据。
    • 初始化 ind=0,将 nn 存入 a[++ind]
    • flag=true 控制迭代,直到发现循环。
    • 提取数字位到 b,排序后用 n1(升序)、n2(降序)做差。
    • 遍历已存数组 a,若 a[i] == n 则输出从 iind 的所有数,置 flag=false
    • 否则将新数继续记录。
  3. 输出:每行结束后输出换行。

代码内容

#include <bits/stdc++.h>
using namespace std;

#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef vector<vector<ll>> vvt;
typedef pair<ll,ll> pll;
const ll N=1e3+10;
const ll INF=1e18;
const ll M=1e6+10;
const ll mod=1e9+7;

ll ind;
ll a[100000], n;

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    while(cin >> n)
    {
        ind = 0;
        a[++ind] = n;
        bool flag = true;
        while(flag)
        {
            ll b[20] = {0}, n1 = 0, n2 = 0;
            ll cnt = 0;
            while(n)
            {
                b[++cnt] = n % 10;
                n /= 10;
            }
            sort(b + 1, b + 1 + cnt);
            for(ll i = 1; i <= cnt; i++) n1 = n1 * 10 + b[i];
            for(ll i = cnt; i >= 1; i--) n2 = n2 * 10 + b[i];
            n = n2 - n1;
            for(ll i = 1; flag && i <= ind; i++)
                if(a[i] == n)
                {
                    flag = false;
                    for(ll j = i; j <= ind; j++) cout << a[j] << " ";
                }
            a[++ind] = n;
        }
        cout << endl;
    }
    return 0;
}