Skip to content

digits 2 - 题解

标签与难度

标签: 构造, 数论, 模运算, 数学, GCD 难度: 1200

题目大意喵~

主人你好,喵~ 这是一道有趣的构造题哦!

题目会给我们一个正整数 nn (其中 1n1001 \le n \le 100)。我们的任务是找到另一个正整数,我们叫它 MM 好了,这个 MM 必须同时满足下面三个条件呐:

  1. MM 的所有位数加起来的总和,必须能被 nn 整除。
  2. MM 本身的值,也必须能被 nn 整除。
  3. MM 的位数不能超过 10410^4 位。

如果能找到这样的数,就把它打印出来。如果有很多个答案,随便打印一个就好啦。如果找不到,就输出 "Impossible"。

举个栗子,如果 n=3n=3,一个可能的答案是 333333。因为它的各位数之和是 3+3+3=93+3+3=9,可以被 33 整除。而且 333333 本身也能被 33 整除。完美,喵!

解题思路分析

这道题要求我们去“构造”一个满足条件的数字,而不是去一个个地暴力搜索,因为数字可能非常大呀,喵~

那我们怎么来构造这个神奇的数字 MM 呢?

一个很自然的想法是,用给定的 nn 本身作为“积木”来搭建我们的 MM。比如说,如果 n=12n=12,我们可以尝试用 "12" 来构造 MM,比如 MM 可以是 121212121212121212121212 …… 这样拼接起来。

让我们看看这种构造方法有什么好处,喵~

假设我们把数字 nn 的字符串形式重复 kk 次,得到新的数字 MM

1. 先看条件二:MM 能否被 nn 整除?

答案是:一定可以的! 为什么呢?让我们来分析一下。 如果 n=12n=12k=3k=3,那么 M=121212M = 121212。 我们可以把它拆开看: M=120000+1200+12M = 120000 + 1200 + 12M=12×10000+12×100+12×1M = 12 \times 10000 + 12 \times 100 + 12 \times 1M=12×(10000+100+1)M = 12 \times (10000 + 100 + 1) 看吧!MM 显然是 1212 的倍数。

推广到一般情况,如果数字 nndd 位,那么将 nn 重复 kk 次,得到的数字 MM 可以表示为:

M=n10d(k1)+n10d(k2)++n10d+nM = n \cdot 10^{d(k-1)} + n \cdot 10^{d(k-2)} + \dots + n \cdot 10^d + n

M=ni=0k110diM = n \cdot \sum_{i=0}^{k-1} 10^{di}

因为 nnMM 的一个因子,所以 MM 一定能被 nn 整除! 太棒了!只要我们用这种方法构造,MM 的值能被 nn 整除这个条件就自动满足了,我们完全不用再担心它了,喵~

2. 再看条件一:MM 的各位数之和能否被 nn 整除?

现在我们只需要专注于这一个条件了。 设数字 nn 本身的各位数之和为 ss。 例如,如果 n=12n=12,那么 s=1+2=3s = 1+2=3。 如果我们将 nn 重复 kk 次来构造 MM,那么 MM 的各位数之和就是 ss 重复了 kk 次,也就是 s×ks \times k。 所以我们的问题就转化为了:找到一个正整数 kk,使得 (s×k)(s \times k) 能够被 nn 整除

怎么找到这个 kk 呢? 最简单的办法,就是让 k=nk=n。这样,各位数之和就是 s×ns \times n,这当然能被 nn 整除了!这个方法一定能找到一个解,而且因为 n100n \le 100,重复 nn 次后的总位数最多是 3×100=3003 \times 100 = 300 位(当 n=100n=100 时),远小于 10410^4 的限制。所以这是一个绝对可行的简单解法。

但是,我们是追求完美的我,能不能找到一个最小的 kk 呢?当然可以! 我们要找最小的正整数 kk,使得 s×ks \times knn 的倍数。 这其实是在要求 s×ks \times kssnn 的公倍数。为了让 kk 最小,我们应该让 s×ks \times k 成为 ssnn最小公倍数(lcm)。 也就是说:

s×k=lcm(s,n)s \times k = \text{lcm}(s, n)

我们又知道一个超级有用的数论公式:

lcm(a,b)×gcd(a,b)=a×b\text{lcm}(a, b) \times \gcd(a, b) = a \times b

其中 gcd(a,b)\gcd(a,b)aabb 的最大公约数。 所以,lcm(s,n)=s×ngcd(s,n)\text{lcm}(s, n) = \frac{s \times n}{\gcd(s, n)}

代入回去:

s×k=s×ngcd(s,n)s \times k = \frac{s \times n}{\gcd(s, n)}

两边同时除以 ss(因为 nn 是正整数,所以 ss 也是正的),我们就得到了 kk 的值:

k=ngcd(s,n)k = \frac{n}{\gcd(s, n)}

这就是我们需要的最小重复次数 kk 啦!

总结一下我们的完美策略:

  1. 读入 nn
  2. 计算出 nn 的各位数之和,记为 ss
  3. 计算 k=ngcd(s,n)k = \frac{n}{\gcd(s, n)}
  4. 将数字 nn 连续输出 kk 次。

这样构造出来的数字,一定满足所有条件,并且是这种构造方式下最短的解!而且因为我们已经证明了总能找到解,所以 "Impossible" 的情况是不会发生的哦。

代码实现

这是我根据上面的思路,精心为你准备的代码哦,喵~

cpp
#include <iostream>
#include <string>
#include <numeric> // C++17 标准库,提供了 std::gcd

// 一个帮助函数,用来计算一个数的各位数之和,喵~
int getDigitSum(int num) {
    int sum = 0;
    std::string s = std::to_string(num); // 转成字符串方便遍历每一位
    for (char c : s) {
        sum += c - '0'; // 把字符'0'-'9'转成数字0-9
    }
    return sum;
}

// 如果你用的不是C++17或更高版本,可以自己实现一个gcd函数
// int gcd(int a, int b) {
//     return b == 0 ? a : gcd(b, a % b);
// }

void solve() {
    int n;
    std::cin >> n;

    // 1. 计算 n 的各位数之和 s
    int digitSum = getDigitSum(n);

    // 2. 计算 n 和 s 的最大公约数
    // C++17 提供了 std::gcd,非常方便!
    int commonDivisor = std::gcd(n, digitSum);

    // 3. 根据我们的公式计算最小重复次数 k
    int numRepetitions = n / commonDivisor;

    // 4. 重复打印 n,一共 k 次
    for (int i = 0; i < numRepetitions; ++i) {
        std::cout << n;
    }
    std::cout << std::endl;
}

int main() {
    // 为了提高 cin 和 cout 的速度,喵~
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    int t;
    // 题目可能包含多个测试用例,虽然样例里没有,但是参考代码中有,这是个好习惯
    std::cin >> t;
    while (t--) {
        solve();
    }

    return 0;
}

复杂度分析

  • 时间复杂度: O(Tnlog10n)O(T \cdot n \log_{10} n) 对于每个测试用例(总共有 TT 个):

    1. getDigitSum(n): 将数字 nn 转为字符串,长度为 O(log10n)O(\log_{10} n),遍历一次,所以是 O(log10n)O(\log_{10} n)
    2. std::gcd(n, digitSum): 计算最大公约数,复杂度大约是 O(logn)O(\log n)
    3. 循环打印:循环次数 k=n/gcd(s,n)k = n / \gcd(s, n),最坏情况下 k=nk=n。每次打印数字 nn 需要 O(log10n)O(\log_{10} n) 的时间。所以这部分是 O(klog10n)O(k \cdot \log_{10} n),也就是 O(nlog10n)O(n \log_{10} n)。 因为 n100n \le 100,所以这个计算量非常小,跑起来飞快,喵~
  • 空间复杂度: O(log10n)O(\log_{10} n) 我们主要用了几个变量来存储 nn, digitSum, k 等,这些都是常数级的空间。getDigitSumstd::to_string 会临时创建一个字符串,其长度为 O(log10n)O(\log_{10} n)。所以空间复杂度非常低。

知识点总结

  • 构造法: 面对求解问题,有时直接构造一个满足条件的解,比搜索解空间更有效。通过分析题目性质,找到一个必能成功的构造模式是关键。
  • 问题简化: 通过“重复拼接 nn” 的构造方式,我们巧妙地将两个条件简化为了一个。这是解题中非常重要的思想哦!
  • 数论基础:
    • 整除性: 理解整除的基本性质。
    • 最大公约数 (GCD): GCD 在数论问题中是常客,是解决许多问题的钥匙。
    • 最小公倍数 (LCM): 通过 lcm(a,b) * gcd(a,b) = a*b 这个公式,我们将问题从找 LCM 转化为了找 GCD,计算起来更方便。

希望这篇题解能帮到你,喵~ 如果还有不懂的地方,随时可以再来问我哦!一起加油,成为算法大师吧!