Skip to content

L. Cracking the Code - 题解

比赛与标签

比赛: Experimental Educational Round: VolBIT Formulas Blitz 标签: implementation, math 难度: *1400

题目大意喵~

主人你好呀~ ٩(ˊᗜˋ*)و 这道题是关于破解一个软件激活码的小故事哦!

事情是这样的:

  1. 程序会给我们一个随机的五位数
  2. 我们要对这个五位数进行一个小小的“魔法”变换:
    • 第一步:数字洗牌! 把这个五位数的数字按照 <第一位><第三位><第五位><第四位><第二位> 的顺序重新排列。比如说,如果数字是 12345,洗牌后就变成了 13542 啦。
    • 第二步:求幂! 把这个新得到的数字,计算它的 5次方
  3. 最后一步:提取密码! 我们只需要这个超级大的5次方结果的最后五位数字作为激活码。

我们的任务就是写一个程序,输入一个五位数,然后输出它对应的激活码,是不是很有趣的说!

解题思路大揭秘!

这道题看起来有点吓人,又是洗牌又是5次方的,数字肯定会变得超级大!但是别怕,猫娘我来带你一步步拆解它,你会发现它其实很温柔的喵~

第一步:数字洗牌要优雅~

题目给的是一个五位数,要对它的每一位进行操作。如果当成数字用除法和取余来分离每一位,会有点小麻烦。最简单直接的方法,就是把它当成一个字符串来读入,喵!

比如,我们读入 std::string s = "12345";

  • 第一位就是 s[0] ('1')
  • 第二位就是 s[1] ('2')
  • ...以此类推

按照题目要求的 <第一位><第三位><第五位><第四位><第二位> 顺序,我们只需要把 s[0], s[2], s[4], s[3], s[1] 拼接起来,就得到新的字符串啦!对于 "12345",新字符串就是 "13542"。

得到新字符串后,我们再把它转换成一个数字,方便进行下一步的数学计算。long long 类型足够大了,用它准没错!

第二步:如何应对天文数字的5次方?

这是本题最核心的难点!洗牌后的数字最大可能是 9999999999 的5次方是一个非常非常非常大的数字,别说 int,就连 long long 也会瞬间溢出(就是装不下啦)。

但是,主人你仔细看题目要求,我们真的需要算出那个完整的天文数字吗?

The answer is the 5 last digits of this result.

不用的!我们只需要最后五位!这简直是天大的好消息!在数学里,求一个数的最后五位,就等价于把它对 100000 取模(求余数)

这里就要用到一个超级重要的数学魔法,叫做模运算(Modular Arithmetic)! 它的一个关键性质是: (a * b) % m = ((a % m) * (b % m)) % m

这个公式告诉我们,两个数相乘再取模,等于它们分别取模后再相乘,最后再取模。这有什么用呢?这意味着我们可以在计算过程中不断地取模,把数字的大小始终控制在 m 以下,从而完美避免溢出!

我们要计算 (新数字^5) % 100000,可以这样做:

  1. result = 1
  2. 循环5次,每次都让 result = (result * 新数字) % 100000
  3. 经过5次循环,result 里存的就是我们想要的最终答案啦!

每一次相乘 result * 新数字,最大也就是 99999 * 99999,大约是 10^10,这个结果 long long 是可以轻松hold住的。所以这个方法既准确又安全,真是太棒了的说!

第三步:完美格式化输出

题目还要求我们输出恰好5位数字。这意味着,如果我们的计算结果是 123,那么应该输出 00123。如果结果是 42,就输出 00042

这个很简单,C++的 <iomanip> 头文件里有专门的工具可以帮我们搞定!

  • std::setw(5):设置输出宽度为5。
  • std::setfill('0'):如果不足5位,用字符 '0' 来填充。

把它们用在输出语句里,就能得到格式完美的答案啦!

代码实现喵~

cpp
#include <iostream>
#include <string>
#include <iomanip> // 喵~ 需要这个头文件来格式化输出哦!
#include <vector>
#include <algorithm>

int main() {
    // 使用这两行可以让C++的输入输出变得飞快,是竞赛中的好习惯呐!
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    // 题目说输入是一个五位数,我们把它当成字符串来读,方便操作每一位数字。
    std::string input_str;
    std::cin >> input_str;

    // 第一步:数字洗牌!
    // 题目要求的顺序是 <第一位><第三位><第五位><第四位><第二位>
    // 对于字符串 "d1d2d3d4d5",洗牌后就是 "d1d3d5d4d2"
    std::string shuffled_str;
    shuffled_str += input_str[0]; // 第一位
    shuffled_str += input_str[2]; // 第三位
    shuffled_str += input_str[4]; // 第五位
    shuffled_str += input_str[3]; // 第四位
    shuffled_str += input_str[1]; // 第二位

    // 把洗牌后的字符串转成数字。用 long long 是为了防止下一步计算时中间结果溢出。
    long long shuffled_num = std::stoll(shuffled_str);

    // 第二步 & 第三步:计算5次方并取最后5位。
    // 直接计算 shuffled_num 的 5 次方会得到一个超级大的数,会超出 long long 的范围。
    // 但是我们只需要最后5位,这等价于对 100000 取模。
    // 我们可以利用模运算的性质:(a * b) % m = ((a % m) * (b % m)) % m
    // 这样就可以在计算的每一步都取模,防止数字变得过大。
    
    long long modulus = 100000; // 我们关心的是最后5位,所以模数是 100000
    long long result = 1;
    
    // 用一个简单的循环来计算 (shuffled_num ^ 5) % modulus
    for (int i = 0; i < 5; ++i) {
        // result * shuffled_num 的最大值约是 10^5 * 10^5 = 10^10,
        // 这个在 64位 long long 的表示范围内(约 9*10^18),所以不会溢出。
        result = (result * shuffled_num) % modulus;
    }

    // 输出必须是恰好5位数。如果结果是 123,要输出 "00123"。
    // 我们用 std::setw(5) 设置宽度为5,std::setfill('0') 设置用'0'填充。
    std::cout << std::setw(5) << std::setfill('0') << result << std::endl;

    return 0;
}

复杂度分析的说

  • 时间复杂度: O(1) 的说。因为输入的长度是固定的5位,所有的操作,包括字符串处理、转换、以及一个固定5次的循环,花费的时间都是一个常数。它不会因为输入数字的不同而改变,所以是 O(1) 呐。
  • 空间复杂度: O(1) 的说。我们只用了几个变量来存储输入字符串、洗牌后的数字和最终结果。使用的内存空间也是固定的,不随输入变化。

知识点与总结

这道题虽然难度是 *1400,但只要掌握了核心技巧,就变成一只温顺的小猫咪啦!

  1. 模运算大法好! 这是本题的灵魂!当遇到求一个巨大计算结果的末几位时,一定要立刻想到取模运算。(a * b) % m = ((a % m) * (b % m)) % m 这个性质一定要牢牢记住,它是处理大数问题的神器!
  2. 字符串处理数字:当需要操作一个数的各位数字时,把它当成字符串来处理,往往比用数学方法 /% 更简单、更直观。
  3. 注意数据范围:在编程时,要时刻对数字的大小有概念,思考会不会溢出。选择合适的数据类型(比如 long long)是避免错误的关键一步。
  4. 输出格式要小心:很多题目对输出格式有严格要求,比如前导零、空格、换行等。一定要仔细阅读题目,使用 setw, setfill 等工具确保输出和样例一模一样。

希望这篇题解能帮到你,主人!如果还有其他问题,随时可以再来问我哦~ 喵~ (ฅ'ω'ฅ)

Released under the MIT License.