You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

195 lines
7.8 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

## [$AcWing$ $424$. 循环](https://www.acwing.com/problem/content/426/)
### 一、题目描述
乐乐是一个聪明而又勤奋好学的孩子。
他总喜欢探求事物的规律。
一天,他突然对数的正整数次幂产生了兴趣。
众所周知,$2$ 的正整数次幂最后一位数总是不断的在重复 $24862486......$
我们说 $2$ 的正整数次幂最后一位的循环长度是 $4$(实际上 $4$ 的倍数都可以说是循环长度,但我们只考虑最小的循环长度)。
类似的,其余的数字的正整数次幂最后一位数也有类似的循环现象:
![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/{year}/{month}/{md5}.{extName}/202309250859879.png)
这时乐乐的问题就出来了:是不是只有最后一位才有这样的循环呢?对于一个整数 $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$
**输入样例**
```cpp {.line-numbers}
32 2
```
**输出样例**
```cpp {.line-numbers}
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$位循环节,
....
```cpp {.line-numbers}
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$ 位。
```cpp {.line-numbers}
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$。
```cpp {.line-numbers}
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
```
```cpp {.line-numbers}
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` 的范围而答案错误。
```cpp {.line-numbers}
#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;
}
```