7.8 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
二、解题思路
一道模拟题,需要用到 高精度乘法。
首先,既然我们要求的是后 k
位的循环节,那前面的循环节我们就不管了。
引理:如果已知后
k − 1
位的循环节,那么后k
位的循环节一定就是它的倍数。
开始模拟找规律,举栗子: (8123, 4)
解释: (
8123
,1
)指8123
的后1
位循环节; (8123
,2
)指8123
的后2
位循环节, ....
8123 1
第 1 次:8123 * 8123 = 65983129
第 2 次:3129 * 8123 = 25416867
第 3 次:6867 * 8123 = 55780641
第 4 次:0641 * 8123 = 05206843
8123 ^ 4 = 4353773312630641
可以发现,在乘第 4
次的时候,最后 1
位出现了循环(数字3
),循环节为 4
,那么最后的答案一定就是 4
的倍数。
解释:如果最后一位想要重复出现,最小的循环数是
4
次。
如果把乘数换一下,不再是傻傻的用8123
去一次次乘,而是换成 0641
,即 8123^4
的后 4
位。这样乘 1
次 0641
就等于乘了 4
次 8123
,而且,乘 0641
还能保证最后 1
位永远是 3
,因为 8123^{4+1}
的最后一位都是 3
。
这样,我们就可以不用再去管后 1
位了,而去处理后 2
位。
8123 2
第 1 次:8123 * 0641 = 05206843
第 2 次:6843 * 0641 = 04386363
第 3 次:6363 * 0641 = 04078683
第 4 次:8683 * 0641 = 05565803
第 5 次:5803 * 0641 = 03719723
0641 ^ 5 = 108215668739201
最后 2
位的循环节是 5
,我们再把乘数换成 9201
。
8123 3
第 1 次:8123 * 9201 = 74739723
第 2 次:9723 * 9201 = 89461323
第 3 次:1323 * 9201 = 12172923
第 4 次:2923 * 9201 = 26894523
第 5 次:4523 * 9201 = 41616123
9201 ^ 5 = 65943979755726446001
8123 4
第 1 次:8123 * 6001 = 48746123
第 2 次:6123 * 6001 = 36744123
第 3 次:4123 * 6001 = 24742123
第 4 次:2123 * 6001 = 12740123
第 5 次:0123 * 6001 = 00738123
我们继续处理完后 3
位和后 4
位,得出了每 1
位的循环节,答案只要把它们累乘起来就可以了,即最后的答案为 4×5×5×5=500
。
至此,我们求出了 8123
后 4
位的循环长度。
由于我们在处理的时候,已经确保了后面的数字都不变,那么我们只要考虑当前这 1
位的循环节就行了,而 1
位的循环节最多为 10
。所以,我们处理每 1
位的时候,就都只要处理 10
次,如果还没出结果,那就直接输出 -1
并退出就行了。
因为我们每 1
位最多处理 10
次,每一次最多要耗费 k^2
的时间,所以时间复杂度约 O(k^3)
,妥妥地能过!
还要注意一点,最后的答案要用高精度,否则会因为超过 long long
的范围而答案错误。
#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;
}