您的位置:首页 > 其它

[集训笔记1st](hdu-4151 Best Solver) (共轭构造/广义斐波那契数列递推/找循环节/快速幂/矩阵快速幂)

2019-07-27 13:57 52 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/qq_43279710/article/details/97496633

试着向教主学习看能不能写个博客记笔记。。
说不定就咕咕咕了…

题意

求 ⌊(5+26)1+2x⌋%M\lfloor\left(\\5+2\sqrt6\right)^{1+2^{x}}\rfloor\%M⌊(5+26​)1+2x⌋%M, x∈[0,232)x\in\left[\\0,2^{32}\right)x∈[0,232), M≤46637M \leq46637M≤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−ba - ba−b等于1的可以这样做)

转化为广义斐波那契数列

广义斐波那契数列的定义:Fib(x)=p∗Fib(x−1)+q∗Fib(x−2)Fib(x)=p * Fib(x-1) + q * Fib(x - 2)Fib(x)=p∗Fib(x−1)+q∗Fib(x−2)
广义斐波那契数列的常数矩阵:
[pq10]\left[\begin{matrix}p& q \\1 & 0\end{matrix} \right][p1​q0​]

论证这样做的可行性:

与FibonocciFibonocciFibonocci数列类比:Fibonacci(n)=15[(1+52)n−(1−52)n]Fibonacci(n) = \frac1{\sqrt5}\left[\left(\frac{1+\sqrt5}{2}\right)^{n}-\left(\frac{1-\sqrt5}{2}\right)^{n}\right]Fibonacci(n)=5​1​[(21+5​​)n−(21−5​​)n]
那是不是我们要求的这个F(n)F(n)F(n)也可以转化为类似的通项公式呢?
比如说:
F(n)=[(5+26)n+(5−26)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]
常数的改变只是因为ppp与qqq的不一样而改变了而已。
然后我们设A=(5+26)n,B=(5−26)nA =\left(5+2\sqrt6\right)^{n}, B=\left(5-2\sqrt6\right)^{n}A=(5+26​)n,B=(5−26​)n
考虑A+BA+BA+B一定是个整数,因为奇数次幂的无理根式相互抵消掉了。
其次因为5−265-2\sqrt65−26​是个小于1的正小数,所以BBB也一定小于1,于是不难想到AAA的整数部分与BBB的整数部分一定是和为1的,那么AAA的小数部分就是1−B1-B1−B,(5+26)n\left(5+2\sqrt6\right)^{n}(5+26​)n的整数部分就是A+B−1A+B-1A+B−1。

所以这个题的答案就转变为了求广义斐波那契数列Fn%MFn\%MFn%M 的值

我们说过FnFnFn是一个广义斐波那契数列,那么要用矩阵快速幂计算的话就一定要求递推公式,这个不难求:
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感觉费马小定理降幂的条件不一定满足,所以我没有用。。。)

引入新的知识:

循环节

广义FibonacciFibonacciFibonacci数列的第nnn项对MMM取模的值总是等于第n%(M−1)∗(M+1))n \%\left(M-1\right)*\left(M+1)\right)n%(M−1)∗(M+1))
对M取模的值,其中(M−1)∗(M+1))\left(M-1\right)*\left(M+1)\right)(M−1)∗(M+1))就是MMM意义下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−2k - 2k−2就是MMM意义下最小的循环节。

这样是不是就解决指数过大的问题了,在对2x2^{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学姐教我的循环节公式,这样就可以用矩阵快速幂做出来了。
做出来一道网络赛题目,蒟蒻有点小激动。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: