题目描述:
斐波那契数列,是一个数学上著名的数列,这个数列{An}是这样的:1 1 2 3 5 8…
除了第一、二项之外,以后每项都是前两项的和
现给定n,请输出An。由于结果可能比较大,请输出An对19999999取余之后的结果
输入示例:
5
输出示例:
5
数据范围:
对于50%的数据,1≤n≤10^5
对于80%的数据,1≤n≤10^8
对于100%的数据,1≤n≤10^10
时间限制:
1s
空间限制:
128MB
题目分析:
对于斐波那契数列的描述,已经在题目描述中描述的非常清楚。但是这次的数据范围非常大,使用朴素的O(n)算法超时对于部分数据来说是必不可少的。因此我们需要寻找对数级别的算法,即O(logn)式算法,这就需要我们对题目进行分析。
首先我们对数据进行分析,对于100%的数据来说,1≤n≤10^10。要想拿到100%的分数,对于数据类型又是一个极大的挑战。long long int可能也没辙。出题者考虑到这一点,决定对数据进行大规模取余运算。这样的问题有两种解决方案,一是对n数据使用高精度计算,这暂时超出了我们目前的学习范围。二是利用初等的数论知识,对取余进行优化。
我们先来学习取余的几个性质:
1)
(a*b)%c=((a%c)*(b%c))%c
2)(a+b)%c=((a%c)+(b%c))%c
我们现假定这两个性质在数学上是正确的而略去其证明。
取余优化之后,我们回到最初的问题,该怎么找到一个时间复杂度为O(logn)的算法,来对计算斐波那契数列进行优化。
我们先来对斐波那契数列的通项公式进行分析:
An={1 (n=1,2)
An-1+an-2(n≥3)
}
好,这里我们就需要一些高等代数的知识了。
我们假定读者已经学过线性代数,并且可以基本掌握矩阵乘法的知识。
两个矩阵相乘首先要满足第一个矩阵的列数等于第二个矩阵的行数,而且要记住一点,矩阵乘法是不满足交换律的。至于矩阵乘法怎么算,假设A为m*p的一个矩阵,B为p*n的一个矩阵,用i表示行数,j表示列数,那A*B的结果中任意一项为
(A∗B)ij=∑pk=1aikbkj=ai1b1j+ai2b2j+...+aipbpj
我们构造以下两个矩阵:
继续:
继续:
以此类推,可以发现矩阵1行1列所代表的数字,是fibonacci数列中的某一项,则fibonacci可以化成算这样的矩阵乘法:
但是我们发现,算一个数的幂的时间复杂度为O(n),依然无法解决我们开头说的找一个对数级别的复杂度算法,因此我们需要一个快速算幂的算法。此处简介快速幂算法。如果n为偶数,那我们可以将其化为 an=an2∗an2 ,如果n为奇数,则对n/2向下取整并再乘以一个n
比如, a3=a1∗a1∗a (1为 32 的向下取整)。
分解到不能再分解后,用数学方法可以证明,快速幂算法的时间复杂度为O(logn)。
long long int quick_power(long long int a, long long int n) // 使用快速幂算法计算 a^n
{
long long int ans = 1;
while (n != 0) // 当 n 不为 0 时循环
{
if (n % 2 == 1) // 判断 n 是否为奇数
{
ans = (ans % 19999999 * a % 19999999) % 19999999; // 如果此时 n 是奇数,那么给结果再乘一个 a
}
a = a * a % 19999999;
n = n / 2; //给 n 整除 2,使规模缩小一半
}
return ans % 19999999;
}
此处就完成了对快速幂的计算。
对快速幂的计算还可以使用基本运算符来计算,此处暂不讲,在讲解快速幂算法的时候展开讲述。
标准代码:
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
struct matrix
{
long long int a[2][2];
matrix()
{
memset(a, 0, sizeof(a));//初始化矩阵
}
void init()//对公共矩阵进行定义
{
a[0][0] = 1;
a[0][1] = 1;
a[1][0] = 1;
a[1][1] = 0;
}
matrix operator*(matrix t)//乘号运算符重载
{
matrix tmp;
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 2; j++)
{
for (int k = 0; k < 2; k++)
{
tmp.a[i][j] += (this->a[i][k]) * t.a[k][j];
}
tmp.a[i][j] %= 19999999;
}
}
return tmp;
}
matrix operator^(long long int t)//运算符重载的矩阵计算
{
matrix tmp;
tmp.a[0][0] = 1;
tmp.a[0][1] = 0;
tmp.a[1][0] = 0;
tmp.a[1][1] = 1;
matrix myself;
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 2; j++)
{
myself.a[i][j] = this->a[i][j];
}
}
while (t)
{
if (t & 1)
{
tmp = tmp * myself;
}
myself = myself * myself;
t >>= 1;
}
return tmp;//快速幂的代码实现,以后会详细讲,此处运用了位运算
}
};
int main()
{
freopen("fibonacci.in", "r", stdin);
freopen("fibonacci.out", "w", stdout);
long long int n = 0;
matrix t, p;
t.init();
cin >> n;
p = t ^ (n - 1);
cout << p.a[0][0] % 19999999;
return 0;
此处采用了运算符重载技术,是C++语言的特性,对于没有学过C++的读者来说,可以编写矩阵乘法函数,其实现方法与该重载运算符类似,此处为笔者参照网上的代码:
#include <iostream>
#include <cstdio>
typedef long long LL;
const LL maxn=1000+10;
const LL mod=19999999;
const int N=2;
using namespace std;
struct Matrix
{
LL m[N][N];
//二元矩阵的建造
};
Matrix A=
{
1,1,
1,0
};
Matrix I=
{
1,0,
0,1
};
Matrix multi(Matrix a,Matrix b)
//矩阵乘法
{
Matrix c;
for(int i=0;i<N;i++)
{
for(int j=0;j<N;j++)
{
c.m[i][j]=0;
for(int k=0;k<N;k++)
c.m[i][j]+=a.m[i][k]*b.m[k][j]%mod;
//利用了取余的运算性质
c.m[i][j]%=mod;
}
}
return c;
}
Matrix power(Matrix A,int k)
//快速幂的实现
{
Matrix ans=I,p=A;
while(k)
{
if(k&1)
{
ans=multi(ans,p);
k--;
}
k>>=1;
p=multi(p,p);
}
return ans;
}
int main()
{
int n;
scanf("%d",&n);
Matrix ans =power(A,n-1);
printf("%lld\n",ans.m[0][0]);
return 0;
}
http://blog.csdn.net/liangzhaoyang1/article/details/51428386
http://codevs.cn/problem/2834/
原稿:贺恩泽 2017.10.01
整理者:李家康 2017.10.01