矩陣快速冪

矩陣快速冪,就是矩陣乘法和快速冪的結合,因此在講矩陣快速冪以前,先講講矩陣乘法和快速冪。spa

矩陣

矩陣的定義

什麼是矩陣?由 \(m \times n\) 個數 \(a_{i,j}(i=1,2,\cdots,m;j=1,2,\cdots,n)\) 排成的一個 \(m\)\(n\) 列的數表code

\[\begin{matrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m,1} & a_{m,2} & \cdots & a_{m,n} \end{matrix}\]

被稱爲 \(m\)\(n\) 列矩陣,簡稱 \(m \times n\) 矩陣。爲了表示這是個總體老是加一個括弧,並用大寫字母表示他,計作:blog

\[A = \left[\begin{matrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m,1} & a_{m,2} & \cdots & a_{m,n} \end{matrix}\right]\]

這個矩陣中的\(m \times n\)個數被稱爲矩陣\(A\)的元素,簡稱元。矩陣\(A\)也可被計作\(A_{m \times n}\)ip

行矩陣(行向量): 只有一行的矩陣。
列矩陣(列向量): 只有一列的矩陣。
同型矩陣: 兩個矩陣\(A\)\(B\)的行數和列數都相等
單位矩陣: 在矩陣乘法中起着很大的做用,至關於乘法中的\(1\),她是個方陣(即行數和列數相同的矩陣),左上角到右下角的對角線(被稱爲主對角線)上的元素都是\(1\),除此以外都是\(0\)。以下就是一個\(4 \times 4\)的單位矩陣:get

\[\left[\begin{matrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{matrix}\right]\]

矩陣加法

咱們定義兩個m \(\times n\)的矩陣\(A\)\(B\)相加爲:io

\[A+B = \left[\begin{matrix} a_{1,1}+b_{1,1} & a_{1,2}+b_{1,2} & \cdots & a_{1,n}+b_{1,n} \\ a_{2,1}+b_{2,1} & a_{2,2}+b_{2,2} & \cdots & a_{2,n}+b_{2,n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m,1}+b_{m,1} & a_{m,2}+b_{m,2} & \cdots & a_{m,n}+b_{m,n} \end{matrix}\right]\]

注意:class

  • 矩陣加法知足交換律和結合律
  • 只有兩個同型矩陣才能夠作加法

矩陣乘法

咱們定義兩個矩陣 \(A_{m \times s}\)\(B_{s \times n}\) 相乘獲得一個矩陣 \(C_{m,n}\),而且二進制

\[c_{i,j} = a_{i,1}b_{1,j}+a_{i,2}b_{2,j} \cdots +a_{i,s}b_{s,j}=\sum^{s}_{k=1}a_{i,k}b_{k,j} \]

並將此計作 \(C=AB\)
注意: 矩陣乘法不知足交換律,但知足結合律和分配律。
\(\tiny{\text{這裏沒放圖是由於放了也看不懂(逃}}\)
若是還不理解的話能夠看這裏
用代碼寫出來就是下面這個亞子:方法

struct matrix
{
	int n,m;//n是行數,m是列數
	ll e[105][105];
}a;
matrix mul(matrix a,matrix b)
{
	matrix ans;
	ans.n=a.n;
	ans.m=b.m;
	for(int i=1;i<=ans.n;i++)
		for(int j=1;j<=ans.m;j++)
			ans.e[i][j]=0;//初始化
	for(int i=1;i<=a.n;i++)
		for(int j=1;j<=b.m;j++)
			for(int k=1;k<=a.m;k++)
				ans.e[i][j]=(ans.e[i][j]+(a.e[i][k]*b.e[k][j])%mod)%mod;//矩陣乘法結果可能很大,因此要取模
	return ans;
}

快速冪

通常咱們求 \(x^y\) 的方法是一個個乘過去,但這種方法太慢了。咱們須要一種新的,快速的方法來求,而這種方法就是快速冪。這裏咱們舉例來理解,就好比求 \(3^{5}\),用 \(ans\) 記錄答案。\(5\) 轉成二進制就是 \({(101)}_2\),那麼咱們從右往左開始枚舉,首先,有個1,那麼咱們就記錄 \(ans=ans \times 3=1 \times 3=3\),而後讓 \(3\) 變成本身的平方,也就是 \(3^2=9\) ,而後接下來是個 \(0\),那就不用乘,而後再讓 \(9\) 變成本身的平方 \(81\),而後又是個 \(1\),因此 \(ans=ans \times 81=3 \times 81=243\)。最後,獲得 \(3^5 = 243\)。手算一下能夠發現這是對的。固然 \(x^y\) 極可能是個很大的數,因此通常都會取模。
代碼實現長這個亞子:im

long long power(long a,long b,long c)
{
	long long ans=1;
	while(b)//沒有枚舉完
	{
		if(b&1) ans=(ans*a)%c;//這一位是不是1
		a=(a*a)%c;
		b>>=1;//走到下一位
	}
	return ans%c;
}

矩陣快速冪

講完了上面這些,那矩陣快速冪就很好理解了!就是把運算改爲矩陣運算。
首先,\(ans\) 和返回值都應該是一個矩陣;而後,\(ans\) 應該初始化爲單位矩陣,緣由上面已經講了;(1.1 矩陣的定義)再而後,裏面的乘法應該改爲矩陣乘法。最後提醒一句,矩陣快速冪必須是個方陣,不然要兩個同型矩陣(本身乘本身,固然是同型矩陣啦~)能夠實現矩陣乘法是不可能的。
代碼實現:

struct matrix
{
	int n,m;
	ll e[105][105];
}a;
matrix mul(matrix a,matrix b)
{
	matrix ans;
	ans.n=a.n;
	ans.m=b.m;
	for(int i=1;i<=ans.n;i++)
		for(int j=1;j<=ans.m;j++)
			ans.e[i][j]=0;
	for(int i=1;i<=a.n;i++)
		for(int j=1;j<=b.m;j++)
			for(int k=1;k<=a.m;k++)
				ans.e[i][j]=(ans.e[i][j]+(a.e[i][k]*b.e[k][j])%mod)%mod;//快速冪的取模就在這裏了!
	return ans;
}//代碼和上面的區別
matrix power(matrix a,ll b)
{
	matrix ans;
	ans.n=a.n;
	ans.m=a.m;
	for(int i=1;i<=ans.n;i++)
		for(int j=1;j<=ans.m;j++)
			if(i==j) ans.e[i][j]=1;
			else ans.e[i][j]=0;//初始化爲單元矩陣
	while(b)
	{
		if(b&1) ans=mul(ans,a);//要把乘換成矩陣乘法哦!
		a=mul(a,a);//這裏也同樣呢
		b>>=1;
	}//快速冪板子
	return ans;
}

實際應用

先掛上神仙同窗的矩陣加速友鏈

矩陣快速冪能夠應用到不少遞推題上。
先放出一道例題:洛谷P1962 斐波那契數列

這題看到 \(n < 2^{63}\) 就知道,直接遞推確定不行,咱們就要用矩陣快速冪來加加速。
咱們知道,這題是由初始的兩個數遞推得來的,那麼咱們有沒有什麼辦法讓他用更快的速度來遞推呢?
固然是用矩陣啦~
因爲遞推 \(F_n\) 須要用到 \(F_{n-1},F_{n-2}\) 兩個項,因此咱們就構建有兩個項的初始矩陣:

\[A=\left[\begin{matrix} F_{n-1} & F_{n-2} \end{matrix}\right]\]

而後,顯而易見地,目標矩陣長這個樣子:

\[B=\left[\begin{matrix} F_n & F_{n-1} \end{matrix}\right]\]

而後,就有了一個遞推式子:

\[AC=B \]

可是咱們不知道這個 \(C\) 是什麼呀。
不要緊,咱們來把上面的式子拆開來:

\[AC=\left[\begin{matrix} f_{n-1} \times c_{1,1}+f_{n-2} \times c_{2,1} & f_{n-1} \times c_{2,1}+f_{n-2} \times c_{2,2} \end{matrix}\right]\]

爲了讓結果等於 \(B\),必需要讓

  • \(f_{n-1} \times c_{1,1}+f_{n-2} \times c_{2,1} = f_{n-1}+f_{n-2}\),因此 \(c_{1,1},c_{1,2}\) 都應該等於 \(1\)
  • \(f_{n-1} \times c_{2,1}+f_{n-2} \times c_{2,2} = f_{n-1}\),因此 \(c_{2,1}=1,c_{2,2}=0\)

因而就獲得了

\[C=\left[\begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix}\right]\]

可是,這樣不仍是 \(O(n)\) 遞推嗎?
觀察一下,能夠發現每次乘的是同一個矩陣,而原來的遞推每次的項是不同的。
這樣有什麼好處呢?咱們能夠先讓全部的 \(C\) 都乘起來,再乘 \(A\) 就好了。
能這樣作,仍是由於矩陣乘法知足結合律
那麼要乘幾個 \(C\) 呢?是 \(n-2\) 次。爲何呢?由於初始矩陣中已經有了兩項:\(F_1\)\(F_2\)

最終代碼以下:

#include<cstdio>
#define ll long long
#define mod 1000000007
using namespace std;
ll n;
struct matrix
{
	int n,m;
	ll e[105][105];
}a,b;
matrix mul(matrix a,matrix b)
{
	matrix ans;
	ans.n=a.n;
	ans.m=b.m;
	for(int i=1;i<=ans.n;i++)
		for(int j=1;j<=ans.m;j++)
			ans.e[i][j]=0;
	for(int i=1;i<=a.n;i++)
		for(int j=1;j<=b.m;j++)
			for(int k=1;k<=a.m;k++)
				ans.e[i][j]=(ans.e[i][j]+(a.e[i][k]*b.e[k][j])%mod)%mod;
	return ans;
}
matrix power(matrix a,ll b)
{
	matrix ans;
	ans.n=a.n;
	ans.m=a.m;
	for(int i=1;i<=ans.n;i++)
		for(int j=1;j<=ans.m;j++)
			if(i==j) ans.e[i][j]=1;
			else ans.e[i][j]=0;
	while(b)
	{
		if(b&1) ans=mul(ans,a);
		a=mul(a,a);
		b>>=1;
	}
	return ans;
}
int main()
{
	scanf("%lld",&n);
	if(n<=2)
	{
		printf("1");
		return 0;
	}
	a.n=2,a.m=2;
	a.e[1][1]=a.e[1][2]=a.e[2][1]=1;
	a=power(a,n-2);
	b.n=1,b.m=2;
	b.e[1][1]=b.e[1][2]=1;
	printf("%lld",mul(b,a).e[1][1]);
	return 0;
}
相關文章
相關標籤/搜索