6.2 KiB
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;
}

