试着向教主学习看能不能写个博客记笔记。。
说不定就咕咕咕了…
题意
求 ⌊ ( 5 + 2 6 ) 1 + 2 x ⌋ % M \lfloor\left(\\5+2\sqrt6\right)^{1+2^{x}}\rfloor\%M ⌊(5+26)1+2x⌋%M, x ∈ [ 0 , 2 32 ) x\in\left[\\0,2^{32}\right) x∈[0,232), M ≤ 46637 M \leq46637 M≤46637
从一个刚会矩阵快速幂的萌新视角看这道题:
难点1:无理数不能在快速幂中求模运算
难点2:指数好大啊。。
难点1的解决办法:
求 ⌊ ( a + b ) n ⌋ % M \lfloor\left(\\a+\sqrt{b}\right)^{n}\rfloor\%M ⌊(a+b)n⌋%M 或者 ⌈ ( a + b ) n ⌉ % M \lceil\left(\\a+\sqrt{b}\right)^{n}\rceil\%M ⌈(a+b)n⌉%M的通用解决办法。( a − b a - b a−b等于1的可以这样做)
转化为广义斐波那契数列
广义斐波那契数列的定义:
F
i
b
(
x
)
=
p
∗
F
i
b
(
x
−
1
)
+
q
∗
F
i
b
(
x
−
2
)
Fib(x)=p * Fib(x-1) + q * Fib(x - 2)
Fib(x)=p∗Fib(x−1)+q∗Fib(x−2)
广义斐波那契数列的常数矩阵:
[
p
q
1
0
]
\left[\begin{matrix}p& q \\1 & 0\end{matrix} \right]
[p1q0]
论证这样做的可行性:
与
F
i
b
o
n
o
c
c
i
Fibonocci
Fibonocci数列类比:
F
i
b
o
n
a
c
c
i
(
n
)
=
1
5
[
(
1
+
5
2
)
n
−
(
1
−
5
2
)
n
]
Fibonacci(n) = \frac1{\sqrt5}\left[\left(\frac{1+\sqrt5}{2}\right)^{n}-\left(\frac{1-\sqrt5}{2}\right)^{n}\right]
Fibonacci(n)=51[(21+5)n−(21−5)n]
那是不是我们要求的这个
F
(
n
)
F(n)
F(n)也可以转化为类似的通项公式呢?
比如说:
F
(
n
)
=
[
(
5
+
2
6
)
n
+
(
5
−
2
6
)
n
]
F(n)=\left[\left(5+2\sqrt6\right)^{n}+\left(5-2\sqrt6\right)^{n}\right]
F(n)=[(5+26)n+(5−26)n]
常数的改变只是因为
p
p
p与
q
q
q的不一样而改变了而已。
然后我们设
A
=
(
5
+
2
6
)
n
,
B
=
(
5
−
2
6
)
n
A =\left(5+2\sqrt6\right)^{n}, B=\left(5-2\sqrt6\right)^{n}
A=(5+26)n,B=(5−26)n
考虑
A
+
B
A+B
A+B一定是个整数,因为奇数次幂的无理根式相互抵消掉了。
其次因为
5
−
2
6
5-2\sqrt6
5−26是个小于1的正小数,所以
B
B
B也一定小于1,于是不难想到
A
A
A的小数部分与
B
B
B的小数部分一定是和为1的,那么
A
A
A的小数部分就是
1
−
B
1-B
1−B,
(
5
+
2
6
)
n
\left(5+2\sqrt6\right)^{n}
(5+26)n的整数部分就是
A
+
B
−
1
A+B-1
A+B−1。
所以这个题的答案就转变为了求广义斐波那契数列 F n % M Fn\%M Fn%M 的值
我们说过
F
n
Fn
Fn是一个广义斐波那契数列,那么要用矩阵快速幂计算的话就一定要求递推公式,这个不难求:
F
(
3
)
=
p
∗
F
(
2
)
+
q
∗
F
(
1
)
F(3) = p * F(2)+q*F(1)
F(3)=p∗F(2)+q∗F(1)
F
(
2
)
=
p
∗
F
(
1
)
+
q
∗
F
(
0
)
F(2) = p * F(1)+q*F(0)
F(2)=p∗F(1)+q∗F(0)
解二元一次方程即可得到
F
(
n
)
=
10
∗
F
(
n
−
1
)
−
F
(
n
−
2
)
F(n) = 10*F(n-1) - F(n-2)
F(n)=10∗F(n−1)−F(n−2)
我们再来解决指数过大的问题
(emmm感觉费马小定理降幂的条件不一定满足,所以我没有用。。。)
引入新的知识:
循环节
广义
F
i
b
o
n
a
c
c
i
Fibonacci
Fibonacci数列的第
n
n
n项对
M
M
M取模的值总是等于第
n
%
(
M
−
1
)
∗
(
M
+
1
)
n \%\left(M-1\right)*(M+1)
n%(M−1)∗(M+1)
项对M取模的值,其中
(
M
−
1
)
∗
(
M
+
1
)
(M-1)*(M+1)
(M−1)∗(M+1)就是
M
M
M意义下fib数列的一个循环节(不一定是最小的,是最小循环节的整数倍。)
PS:最小循环节的寻找方法
思路是打表,等到 F [ k ] = F [ 2 ] & & F [ k − 1 ] = F [ 1 ] F[k]=F[2]\&\&F[k-1]=F[1] F[k]=F[2]&&F[k−1]=F[1]时, k − 2 k - 2 k−2就是 M M M意义下最小的循环节。
这样是不是就解决指数过大的问题了,在对 2 x 2^{x} 2x求快速幂时,对循环节取模,然后再矩阵快速幂,就可以得到答案啦。
AC代码(递推)
#include<cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
ll MOD, x;
ll m;
int f[500000], r[500000];
ll quick_pow(ll x, ll a) {
ll res = x, ans = 1;
do {
ans = (a & 1) ? ((ans %= MOD) * (res %= MOD)) % MOD : ans;
res *= (res %= MOD);
} while (a >>= 1);
return ans % MOD;
}
void init() {
if (r[m] != -1)
return;
f[0] = 2 % m;
f[1] = 10 % m;
for (int i = 2;; ++i) {
f[i] = ((10 * f[i - 1] - f[i - 2]) % m + m) % m;
if (f[i - 1] == f[0] && f[i] == f[1]) {
r[m] = i - 1;
break;
}
}
}
int main() {
int t;
scanf("%d", &t);
memset(r, -1, sizeof(r));
for (int i = 1; i <= t; i++) {
scanf("%lld%lld", &x, &m);
init();
MOD = r[m];
ll k;
k = (quick_pow(2, x) + 1) % MOD;
f[0] = 2 % m;
f[1] = 10 % m;
for (int i = 2; i <= k; ++i)
f[i] = ((10 * f[i - 1] - f[i - 2]) % m + m) % m;
ll ans = (f[k] - 1 + m) % m;
printf("Case #%d: %lld\n", i, ans);
}
return 0;
}
AC代码2(矩阵快速幂)
嫖一手大佬舍友的代码
#include<iostream>
#include<cstring>
#include<stdio.h>
using namespace std;
typedef long long ll;
const int maxn = 2;
ll n;
struct mat {
ll mp[maxn][maxn];
};
mat mul(mat a, mat b, int mod) {
mat temp;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
temp.mp[i][j] = 0;
for (int k = 0; k < 2; k++) {
temp.mp[i][j] += (a.mp[i][k] * b.mp[k][j] % mod + mod) % mod;
}
}
}
return temp;
}
mat quick(ll b, ll mod) {
mat a = {10, -1, 1, 0};
mat ans = {98, 0, 10, 0};
while (b) {
if (b & 1)
ans = mul(a, ans, mod);
a = mul(a, a, mod);
b >>= 1;
}
return ans;
}
ll quick2(ll a, ll b) {
ll ans = 1;
while (b) {
if (b & 1)
ans = (ans % n * a % n) % n;
a = a * a % n;
b >>= 1;
}
return ans % n;
}
int main() {
int n0;
cin >> n0;
int count = 1;
while (n0--) {
ll a, mod, m;
cin >> a >> mod;
n = ((mod - 1) * (mod + 1));
m = quick2(2, a);
mat temp = quick(m, mod);
ll result = (temp.mp[1][0] - 1) % mod;
printf("Case #%d: %d", count++, result);
cout << endl;
}
}
这个题如果掌握了知识点的话,就是一道考验背模板能力的题。
但是因为不会知识点被学长用来提高AK难度了嘤嘤嘤。
感谢lgz学长教我的打表思路,用递推拿了一血。
感谢wjy学姐教我的循环节公式,这样就可以用矩阵快速幂做出来了。
做出来一道网络赛题目,蒟蒻有点小激动。