这其实是两道题目,但是他们量都题目的思路是在是相像,而且炒鸡厉害。
Fibonacci 第 n 项
题目描述
大家都知道 Fibonacci 数列吧,f1=1,f2=1,f3=2,f=3...fn=fn-1+fn-2,
f1=1,f2=1,f3=2,f4=3,…,fn=fn?1+fn?2。
现在问题很简单,输入 n 和 m,求 fn mod m
输入格式
输入 n,m。
输出格式
输出 fn mod m
样例输入
5 1000
样例输出
5
Fibonacci 前 n 项和
题目描述
大家都知道 Fibonacci 数列吧,f1=1,f2=1,f3=2,f=3...fn=fn-1+fn-2,
f1=1,f2=1,f3=2,f4=3,…,fn=fn?1+fn?2。
现在问题很简单,输入 n 和 m,求 {fn}的前n项sn mod m
输入格式
输入 n,m。
输出格式
输出 fn mod m
样例输入
5 1000
样例输出
12
这两个的数据都是很惊人,都是$$n<=2*10^{9}$$
有木有一种恐怖袭击的来临,很显然我们不能用传统的思想来,因为数组根本存不下,要不然我写这个有什么意思 !
所以这里就要用到矩阵乘法
切入正题,核核。首先是Fibonacci 第 n 项这道题,我们可以知道:
f[i]=1*f[i-1]+1*f[i-2]
f[i-1]=1*f[i-1]+0*f[i-2]
把他们看成一个矩阵,转换为
$$
\begin{pmatrix}
f[i] \
f[i-1]
\end{pmatrix}
=\begin{pmatrix}
1 & 1 \
1 & 0
\end{pmatrix}*
\begin{pmatrix}
f[i-1] \
f[i-2]
\end{pmatrix}
$$
因为
f[i]=1*f[i-1]+1*f[i-2]
f[i-1]=1*f[i-1]+0*f[i-2]
所以可以根据矩阵乘法(我默认你矩阵乘法懂了 )得出刚刚的那个矩阵
接着我们又可以得到:
$$
\begin{pmatrix}
f[i] \
f[i-1]
\end{pmatrix}
=\begin{pmatrix}
1 & 1 \
1 & 0
\end{pmatrix}
\begin{pmatrix}
1 & 1 \
1 & 0
\end{pmatrix}
\begin{pmatrix}
f[i-2] \
f[i-3]
\end{pmatrix}
$$
然后这样一直化简,可以得出一般式
$$
\begin{pmatrix}
f[i] \
f[i-1]
\end{pmatrix}
=\begin{pmatrix}
1 & 1 \
1 & 0
\end{pmatrix}^{n-2}
\begin{pmatrix}
f[2] \
f[1]
\end{pmatrix}
$$
然后因为矩阵可以用乘法结合律,所以我们可以用快速幂,把
$$
\begin{pmatrix}
1 & 1 \
1 & 0
\end{pmatrix}
$$
看成是一个数x,那么我们就可以用快速幂去求$$x^{n-2}$$的值啦。大大减少了空间有木有【0_0】
然后上代码(我都说的这么清楚了还用得着看代码?! )
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
typedef signed long long ll;
struct node
{
ll p[3][3];
node(){memset(p,0,sizeof(p));}
};
int n;
ll M;
node A;
node plus(node a,node b)
{
node c;
for(int i=1;i<=2;i++)
for(int j=1;j<=2;j++)
for(int k=1;k<=2;k++)
c.p[i][j]+=a.p[i][k]*b.p[k][j],c.p[i][j]%=M;
return c;
}
node plus1(node a,node b,int n,int m,int p)
{
node c;
for(int i=1;i<=n;i++)
for(int j=1;j<=p;j++)
for(int k=1;k<=m;k++)
c.p[i][j]+=a.p[i][k]*b.p[k][j],c.p[i][j]%=M;
return c;
}
node mi(int n)//快速幂运算,也可以用while,因为这个有可能会爆栈
{
if(n==1)return A;
node t=mi(n/2);
if(n%2==1)return plus(plus(t,t),A);
else return plus(t,t);
}
int main()
{
A.p[1][1]=1;A.p[1][2]=1;
A.p[2][1]=1;A.p[2][2]=0;
scanf("%d %llu",&n,&M);
node c=mi(n-2),ha;//矩阵的幂运算
ha.p[1][1]=1;ha.p[2][1]=1;//因为f[1]=f[2]=1,这是题目给出的
c=plus1(c,ha,2,2,1);
printf("%llu\n",c.p[1][1]%M);
return 0;
}
然后下一题跟这一题目的思路很像,因为题目说s[i]={f[i]}前i项的和,所以可以得出:
//这里我们用s[n-1],f[n],f[n-1]来表示这s[n],f[n+1],f[n]
s[n]=1*s[n-1]+1*f[n]+0*f[n-1]
//因为s[n]是前n个肥波数列的前缀和,所以他就是前n-1的前缀和加上第n个肥波数列
f[n+1]=0*s[n-1]+1*f[n]+1*f[n-1]
//那第n项的下一个就是f[n]+f[n-1]
f[n]=0*s[n-1]+1*f[n]+0*f[n-1]
//f[n]就为f[n]
//这样我们就建立起了关系
这种思路很好理解,但很难想哎!
然后像上一题一样把他换成矩阵,就得
$$
\begin{pmatrix}
s[n] \
f[n+1] \
f[n]
\end{pmatrix}=
\begin{pmatrix}
1 & 1 & 0 \
0 & 1 & 1 \
0 & 1 & 0
\end{pmatrix}*
\begin{pmatrix}
s[n-1] \
f[n] \
f[n-1]
\end{pmatrix}
$$
然后可以一直化简到一般式
$$
\begin{pmatrix}
s[n] \
f[n+1] \
f[n]
\end{pmatrix}=
\begin{pmatrix}
1 & 1 & 0 \
0 & 1 & 1 \
0 & 1 & 0
\end{pmatrix}^{n-1}
\begin{pmatrix}
s[1] \
f[2] \
f[1]
\end{pmatrix}
$$
一样的思路,上代码!
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
typedef signed long long ll;
struct node
{
ll p[4][4];
node(){memset(p,0,sizeof(p));}
};
int n;
ll M;
node A;
node plus(node a,node b)
{
node c;
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
for(int k=1;k<=3;k++)
c.p[i][j]+=a.p[i][k]*b.p[k][j],c.p[i][j]%=M;
return c;
}
node plus1(node a,node b,int n,int m,int p)
{
node c;
for(int i=1;i<=n;i++)
for(int j=1;j<=p;j++)
for(int k=1;k<=m;k++)
c.p[i][j]+=a.p[i][k]*b.p[k][j],c.p[i][j]%=M;
return c;
}
node mi(int n)
{
if(n==1)return A;
node t=mi(n/2);
if(n%2==1)return plus(plus(t,t),A);
else return plus(t,t);
}
int main()
{
A.p[1][1]=1;A.p[1][2]=1;A.p[1][3]=0;
A.p[2][1]=0;A.p[2][2]=1;A.p[2][3]=1;
A.p[3][1]=0;A.p[3][2]=1;A.p[3][3]=0;
scanf("%d %llu",&n,&M);
node c=mi(n-1),ha;
ha.p[1][1]=1;ha.p[2][1]=1;ha.p[3][1]=1;
c=plus1(c,ha,3,3,1);
printf("%llu\n",c.p[1][1]%M);
return 0;
}
总结:矩阵乘法大大压缩了空间
本文思路摘自《一本通提高篇》
原文地址:https://www.cnblogs.com/candy067/p/11401960.html