PS:如果读过题了可以跳过题目描述直接到题解部分
提交链接1:未来计算 3689 佳佳的 Fibonacci
提交链接2:未来算算 1730 佳佳的 Fibonacci
题目
题目描述
佳佳对数学,尤其对数列十分感兴趣。在研究完 Fibonacci 数列后,他创造出许多稀奇古怪的数列。例如用 S(n) 表示 Fibonacci 前 n 项和 mod m 的值,即 S(n)=(F1 +F2 +...+Fn ) mod m,其中 F1 =F2 =1,Fi =Fi−1 +Fi−2 。可这对佳佳来说还是小菜一碟。
终于,她找到了一个自己解决不了的问题。用 T(n)=(F1 +2F2 +3F3 +...+nFn ) mod m 表示 Fibonacci 数列前 n 项变形后的和 mod m 的值。
现在佳佳告诉你了一个 n 和 m,请求出 T(n) 的值。
输入
输入数据包括一行,两个用空格隔开的整数 n,m。
输出
仅一行,T(n) 的值。
样例
输入
5 5
输出
1
提示
样例解释
T(5)=(1+2×1+3×2+4×3+5×5) mod 5=1
数据范围与提示
对于 30% 的数据,1≤n≤1000;
对于 60% 的数据,1≤m≤1000;
对于 100% 的数据,1≤n,m≤2^31 −1。
题解
矩阵乘法
具体内容详见我之前的文章:矩阵乘法
推导矩阵
其实吧,看到是斐波那契,多练过几道题的都知道是考矩阵乘法,但难的地方在于矩阵怎么写。
对于一般的斐波那契数列,我们用来推单项的矩阵通常都长这样:
但对于这道题的这个矩阵我们先要分析这个问题的构造。
因为我们是从前往后递推(显然的废话),我一开始的想法是用一个矩阵在更新原矩阵的时候再用另一个矩阵来更新那个矩阵对于原矩阵的数乘的值(我知道很绕,所以这一句话看不懂也没关系)
但这样就不能使用快速幂了。
所以我换了一个思路,用全部的和来减。大概是这样:
最后用第二排的乘 n 以后减去第一排的那个就是答案了。
“正解”
这道题其实还有一个做法,被称为正解。
就是当我们假设时,我们会发现
。
所以我们可以把 T(n) 反复代换,最后
相当于就可以只需要算出两个斐波那契的单项就可以了。
(但我个人认为,反正都是用矩阵快速幂,两种好像都差不多)
代码实现
//未来计算 3689 未来算算 佳佳的 Fibonacci
#include<iostream>
#include<cstdio>
using namespace std;
long long n,m;
long long a[6][6];
long long b[6][6];
long long c[6][6];
long long ans;
long long x;
int main(){
scanf("%lld%lld",&n,&m);
a[0][0]=a[0][1]=a[1][1]=a[1][2]=a[2][3]=a[3][2]=a[3][3]=1;
for(int i=0;i<=3;++i){
for(int j=0;j<=3;++j){
b[i][j]=(i==j);
}
}
x=n;
while(n){
if(n&1){
for(int i=0;i<=3;++i){
for(int j=0;j<=3;++j){
for(int k=0;k<=3;++k){
c[i][j]=(c[i][j]+(b[i][k]%m*a[k][j]%m)%m)%m;
}
}
}
for(int i=0;i<=3;++i){
for(int j=0;j<=3;++j){
b[i][j]=c[i][j];
c[i][j]=0;
}
}
}
for(int i=0;i<=3;++i){
for(int j=0;j<=3;++j){
for(int k=0;k<=3;++k){
c[i][j]=(c[i][j]+(a[i][k]%m*a[k][j]%m)%m)%m;
}
}
}
for(int i=0;i<=3;++i){
for(int j=0;j<=3;++j){
a[i][j]=c[i][j];
c[i][j]=0;
}
}
n>>=1;
}
ans=((((b[1][2]+b[1][3])%m*x%m)%m-(b[0][2]+b[0][3])%m)%m+m)%m;
printf("%lld\n",ans);
return 0;
}