Files
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

6.2 KiB
Raw Permalink Blame History

AcWing 424. 循环

一、题目描述

乐乐是一个聪明而又勤奋好学的孩子。

他总喜欢探求事物的规律。

一天,他突然对数的正整数次幂产生了兴趣。

众所周知,2 的正整数次幂最后一位数总是不断的在重复 2,4,8,6,2,4,8,6...... 我们说 2 的正整数次幂最后一位的循环长度是 $4$(实际上 4 的倍数都可以说是循环长度,但我们只考虑最小的循环长度)。

类似的,其余的数字的正整数次幂最后一位数也有类似的循环现象:

这时乐乐的问题就出来了:是不是只有最后一位才有这样的循环呢?对于一个整数 n 的正整数次幂来说,它的后 k 位是否会发生循环?如果循环的话,循环长度是多少呢? 

注意: 

  • 如果 n 的某个正整数次幂的位数不足 $k$,那么不足的高位看做是 $0$。
  • 如果循环长度是 $L$,那么说明对于任意的正整数 $a$,n 的 a 次幂和 a+L 次幂的最后 k 位都相同。

输入格式 输入文件只有一行,包含两个整数 n 和 $k$,n 和 k 之间用一个空格隔开,表示要求 n 的正整数次幂的最后 k 位的循环长度。

输出格式 输出文件包括一行,这一行只包含一个整数,表示循环长度。

如果循环不存在,输出 −1。

数据范围 1≤n≤10100,1≤k≤100

输入样例:

32 2

输出样例:

4

举栗子:

32^1=32 ,32^2=1024,32^3=32768,32^4=1048576 32^5=33554432,

此时发现后$2$位开始循环,而循环节的长度是4

二、解题思路

1、本题要求的是末 k 位的循环节长度。

需要从最低位开始判断:

假设一个数的末尾四位数字为 $1234$,要求 1234 的循环节。


  • ① 先判断最后一位 4 的循环节,假设找到是$x$,即 (1234)^x 的个位保持 4 不变。

  • ② 再找倒数第二位 3 的循环节。为了保证每次循环最后一位都保持 4 不变,故每一轮要以 (1234)^x 为基准进行累乘。假设找到是 $y$,则说明 ((1234)^x)^y 的末两位保持 34 不变。

  • ③ 再找倒数第三位 2 的循环节。为了保证每次循环末两位保持 34 不变,故每一轮要以 ((1234)^x)^y 为基准进行累乘。假设找到是 $z$,则说明 $(((1234)^x)^y)^z$的末三位保持 234 不变。

  • ④再找倒数第四位 1 的循环节。为了保证每次循环末三位保持 234 不变,故每一轮要以 (((1234)^x)^y)^z 为基准进行累乘。假设找到是 $r$,则说明 ((((1234)^x)^y)^z)^r 的末四位保持 1234 不变。 故 1234 的循环节为 x ∗ y ∗ z ∗ r

将上述 (1234)^x,$((1234)^x)^y$,$(((1234)^x)^y)^z$,$((((1234)^x)^y)^z)^r$ 称为 每一轮的累乘单位。

2、下表描述了基本判断过程:

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

const int N = 110;

int K;            // 求最后K位的循环节长度是多少
int a[N];         // 原始数组
int b[N];         // 工作数组
int cnt[N] = {1}; // 次数,也就是答案
int st[10];

// 高精乘高精,限长K,结果保存到a数组中
void mul(int a[], int b[]) {
    int c[N] = {0};             // 临时数组
    for (int i = 0; i < K; i++) // 只保留尾部K个,对应高精度数组,就是0~K-1
        for (int j = 0; j < K; j++)
            if (i + j < K) c[i + j] += a[i] * b[j]; // 防止越界,需要加上i+j<K的限制

    for (int i = 0, t = 0; i < K; i++) {
        t += c[i];     // 进位与当前位置的和
        a[i] = t % 10; // 余数保留下来
        t /= 10;       // 进位
    }
}

// 输出高精度结果
void print(int a[]) {
    int i = N - 1;
    while (i > 0 && !a[i]) i--;    // 去除前导零
    while (i >= 0) cout << a[i--]; // 逐位输出
}

int main() {
    string s;      // 这里之所以用string类型,是为了照顾高精度同学
    cin >> s >> K; // 求最后k个长度的循环节长度是多少

    // 放入高精度计算数组,注意是倒着放的,下标从0开始
    for (int i = s.size() - 1; i >= 0; i--) a[s.size() - 1 - i] = s[i] - '0';

    for (int i = 0; i < K; i++) { // 执行K轮,从后往前,每轮固定一个尾部数字
        memset(st, 0, sizeof st); // 某个数字是不是出现过,桶
        memcpy(b, a, sizeof a);   // b数组初始化

        int j;
        for (j = 1; j <= 10; j++) { // 最多执行10次
            mul(b, a);              // 开始乘a

            // 对于高精度数组而言,头部即数字尾部
            // 随着轮次的增加,我们希望检查的位置也发生了变化,位置下标为i
            // 如果当前计算的结果中,当前的计算位置,出现与原数字一样的数字,那么就是找到了循环节,此时的j就是我们想要收集的
            if (b[i] == a[i]) break;

            // 如果某个数字重复出现,却一直没有和原始的一样,表示它自己的循环轨迹和原始的不一样,永远也不可以一样了,无解
            if (st[b[i]]) {
                puts("-1");
                exit(0);
            };
            st[b[i]] = 1; // 记录出现过的数字
        }

        // ①  累计次数
        int c[N] = {j}; // 需要乘的次数,这里有用高精度乘高精度模拟高精度乘低精度
        // 注意:即使j=10,上面的高精度乘法依然是有效的,因为在高精度乘法的内部采用的是模10进位的办法
        mul(cnt, c); // 将次数累乘起来

        // ② 变更乘数
        memcpy(b, a, sizeof a);                // 重新装载原始数字
        for (int k = 1; k < j; k++) mul(b, a); // 反复乘j-1次
        memcpy(a, b, sizeof b);                // a升级了
    }

    print(cnt); // 输出高精度数组内容
    return 0;
}