《入门经典》——9.2
Uva11582:
基于斐波那契数列,给出a,b,mod,让你求解f(a^b) % mod的结果。
分析:这是一道很看基本功的简单数论题目。
数据类型:题目给出的a、b的范围最大可以取到2^64,因此这里凡是用到a、b的运算的地方都要用ULL(unsigned long long),经过调试显示long long这个数据类型都不够。
算法分析:本身斐波那契数列在一定项之后就变得异常大,结合同余运算的性质,因此这里我们考虑直接基于余数进行迭代求余。 而由于a、b的取值范围,我们显然不能打表打到a^b,因此我们推测取余结果必然存在周期性。
进一步更详细的分析周期性存在的合理性:令F(i) = f(i) % mod , 这里f(i)是斐波那契数列。我们得到F(n)的线性序列,我们从二元组<F(i),F(i+1)>来看,在根据递推公式往后生成的时候,一旦<F(i),F(i+1)>曾经出现过,这就表明余数的线性序列的周期是i-1。而二元组最多有mod x mod个,因此F(i)的序列长度超过mod x mod之后,必然会出现周期性。
总结起来,在这里处理数据巨大的技巧,归结为下面几个方面:
- 将f(i)映射到F(i) = f(i) % mod.
- 在证明了F(i)分布的周期性必然存在之后,寻找F(i)分布的周期性。
- 利用快速幂取模找到a^b处于一个周期中那个位置,即计算a^b % M.其中M是F(i)分布的周期M.
简单的参考代码如下:
#include<cstdio> #include<vector> #include<algorithm> #include<iostream> using namespace std; const int maxn = 1001; vector<int>f[maxn]; typedef unsigned long long ULL; void init() { for(int i = 2;i <= 1000;i++) { int mod = i; int a = 0 , b = 1 , c = (a+b)% mod; f[i].push_back(a); f[i].push_back(b); f[i].push_back(c); while(!(b == 0 && c == 1)) { a = b; b = c; c = (a % mod +b % mod) % mod; f[i].push_back(c); } f[i].pop_back(); f[i].pop_back(); } } ULL quick_mod(ULL a,ULL b, int _m) { ULL ans = 1; while(b){ if(b&1){ ans = (ans%_m) * (a%_m) % _m; b--; } b /= 2; a = (a%_m)*(a%_m)%_m; } return ans; } ULL n , m ; int main() { int t; int Mod; init(); scanf("%d",&t); while(t--) { cin >> n >> m >> Mod; if(Mod == 1) {printf("0\n");continue;} int _m = f[Mod].size(); ULL temp = quick_mod(n , m , _m); printf("%d\n",f[Mod][temp]); } }