算法
(高精度,数学,数论,递推) $O(10k^3)$
引理1: 前 $k + 1$ 位的所有循环节长度,一定是 前 $k$ 位循环节长度的子集。
证明:
- 如果前 $k$ 位都不相同,那么前 $k + 1$ 位一定也不相同。
引理2: 假设最小循环节长度是 $t$,那么所有循环节长度一定都是 $t$ 的整数倍。
证明:
- 假设有循环节长度 $r$ 不是 $t$ 的倍数,那么令 $p = r \; mod \; t$ ,则 $p < t$,且对于任意正整数a,均有 $n^a \equiv n^{a + r} \equiv n^{a + r - t} \equiv n^{a + r - 2t} \equiv … \equiv n^{a + p} \; mod \; 10^k$,所以 $p$ 也是循环节,与 $t$ 是最小循环节矛盾。
因此我们可以从小到大一位一位递推出前 $k$ 位的循环节长度。
假设已经求出前 $k_0$ 位的循环节长度 $t_0$,那当我们求前 $k_0 + 1$ 位的循环节长度时,
只需枚举 $n \times n^{t_0}, n \times n^{2t_0}, n \times n^{3t_0}, …, n \times n^{10t_0}$ 是否和 $n$ 相同即可。
注意这里只需要枚举前10项,因为前 $k_0$ 位是固定不变的,只有第 $k_0 + 1$ 位会变化,因此最多只有10种不同选择。
时间复杂度
总共需要递推 $k$ 位,每次递推时最多需要枚举10项,每次枚举时会用到高精度乘法。
这里高精度乘法没有用FFT加速,因此时间复杂度是 $O(10k^3)$。
C++ 代码
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int m;
int nums[N], power[N];
void mul(int c[], int a[], int b[])
{
static int temp[N];
memset(temp, 0, sizeof temp);
for (int i = 0; i < m; i ++ )
for (int j = 0; j < m; j ++ )
if (i + j < m)
temp[i + j] += a[i] * b[j];
for (int i = 0, t = 0; i < m; i ++ )
{
t += temp[i];
temp[i] = t % 10;
t /= 10;
}
memcpy(c, temp, sizeof temp);
}
void mul(int c[], int a[], int b)
{
for (int i = 0, t = 0; i < m; i ++ )
{
t += a[i] * b;
c[i] = t % 10;
t /= 10;
}
}
int main()
{
string str;
cin >> str >> m;
for (int i = 0, j = str.size() - 1; j >= 0; j --, i ++ ) nums[i] = str[j] - '0';
memcpy(power, nums, sizeof nums);
int per[N] = {1};
int p1[N], pn[N]; // p1 = 1 * p^(kt), pn = n * p^(kt)
for (int k = 1; k <= m; k ++ )
{
memcpy(pn, nums, sizeof nums);
memset(p1, 0, sizeof p1);
p1[0] = 1;
int r = 0;
while (r <= 10)
{
mul(pn, pn, power);
mul(p1, p1, power);
r ++ ;
if (pn[k - 1] == nums[k - 1]) break;
}
memcpy(power, p1, sizeof p1);
if (r > 10)
{
memset(per, 0, sizeof per);
per[0] = -1;
break;
}
mul(per, per, r);
}
int k = m;
while (!per[k]) k -- ;
while (k >= 0) cout << per[k -- ];
return 0;
}
Y总在视频中讲的循环节,严格来讲是不准确的。循环节在数学中的定义是循环数位的长度。如0.365365365,那么这个数的循环节就是3。而本题中要求的一组数,在连续L个数中的后K位数组成一组最小循环单位。如123,134,145,153,214,325,这组数的数位k是1(最后一位数),最小循环长度L是3。明白了定义,那么我们知道要L增加,k必然增加,并且只需判断第k位数是否循环(因为前k-1位数已经判断);若k增大,L可能增大,且L[k]一定是L[k-1]的倍数。
n^(a+r)≡n^(a+r−t)这两个为啥同余啊 因为 n^(a+r) = n^(a+t + r-t) = n^(a+t) * n ^(r-t)
而n^(a+t)是与 n^a同余的, 所以n^(a+r) ≡ n ^a * n^(r-t) = n ^(a+r-t)同余, 对吗?
对滴。
大佬,请给我详细的注释
好难猜啊在性质
明白了循环位数和循环单位长度的关系,此题代码是对nn^rT的解析。代码中的pn表示nn^rT,p1表示n^rT,per表示r,power表示n^T
大师,我是小白
有没有简单一点
+1,难