「学习笔记」矩阵快速幂

一.快速幂

P1226 【模板】快速幂||取余运算

快速幂用来解决这样的式子 \(a^b\),大家应该都会,我就放个代码。

ll ksm (ll a, ll b) {//a^b
	ll res = 1;
	while (b) {
		if (b & 1) {
			res = res * a;
		}
		b >>= 1;
		a = a * a;
	}
	return res;
}

Ybtoj【例题1】序列的第k个数

这道题就可以直接根据前三项判断是等差数列还是等比数列。

等差数列 \(a_n=a_1+(n-1)×q\),等比数列 \(a_n=a_1×q_{n-1}\)\(q\) 为公差。


二.矩阵

概念1.单位矩阵

主对角线上的元素都为 \(1\),其余元素都为 \(0\)\(n\) 阶矩阵称为 \(n\) 阶单位矩阵。

单位矩阵的重要点在于:单位矩阵的任意次方都等于它本身。

如图,这就是一个三阶单位矩阵:
image

在矩阵快速幂转移时,可以将其设为初始矩阵。


概念2.矩阵加/减法

两个矩阵相加/减,就将其对应位置上的数相加/减。

\(A,B\) 为长宽相等的矩阵,矩阵 \(C=A±B\),那么 \(A_{i,j}±B_{i,j}=C_{i,j}\)

下图就是一个加法的例子。

image


概念3.矩阵乘法

设矩阵 \(C=A×B\),有几个要求。

\(1.\) \(A\) 的列数等于 \(B\) 的行数。

\(2.\) \(A\)\(n×r\) 的矩阵, \(B\)\(r×m\) 的矩阵,那么 \(C\) 就是 \(n×m\) 的矩阵。

\(3.\) \(C_{i,j}=\sum ^{r}_{k=1}A_{i,k}B_{k,j}\)

如图就是矩阵乘法的例子:
image

我们显然可以得到一个结论:

矩阵乘法满足结合律,不满足交换律。

这里给出 \(O(n^3)\) 的矩阵乘法代码:

inline void Mul (int f[][], int x[][], int y[][]) {//计算矩阵x乘y=f。
	int t[N][N];//临时记录答案矩阵。
	for (int i = 0; i < n; i ++) {
		for (int j = 0; j < n; j ++) {
			t[i][j] = 0;//清空答案矩阵。
		}
	}
	for (int i = 0; i < n; i ++) {
		for (int j = 0; j < n; j ++) {
			for (int k = 0; k < n; k ++) {
				t[i][j] = t[i][j] + x[i][k] * y[k][j];//矩阵乘法法则。
			}
		}
	}
	for (int i = 0; i < n; i ++) {
		for (int j = 0; j < n; j ++) {
			f[i][j] = t[i][j];//复制答案。
		}
	}
}

概念4.方阵乘幂

方阵是一个长和宽相等的矩阵。

将方阵 \(A\) 自乘 \(n\) 次,记作 \(A^n\)

上文说到矩阵乘法满足结合律,那么我们就可以将方阵乘幂利用快速幂的思想优化。


三.矩阵快速幂的引入

P3390 【模板】矩阵快速幂

先解决这道模板题。

给定矩阵 \(A\),求 \(A^k\)

只需要将快速幂中的数字变为矩阵即可,乘法变为矩阵乘法。

给出一个模板,这是以后做题的基础。

inline void qpow (int T) {
    initans ();//这里是将ans初始化为单位矩阵。
    
    while (T) {
        if (T & 1) {
            Mul (ans, ans, a);
            //ans=ans*a。
        }
        
        Mul (a, a, a);
        //a=a*a。
        
        T >>= 1;
    }
}

观察一下,是不是与原来的快速幂模板一模一样?


Ybtoj【例题2】斐波那契数列

P1962 斐波那契数列

求斐波那契数列的第 \(n\)\(\bmod10^9+7,n<2^{63}\)

\(O(n)\) 递推显然不可做,我们就需要采用矩阵快速幂来进行优化。

我们的思路在于:

我们将最后求的答案变为一个矩阵 \(B\),构建一个初始矩阵 \(A\) 和一个转移矩阵 \(base\),使 \(A×base=B\)

那么,我们设答案矩阵 \(fib(n)=\begin{bmatrix} f_{n} & f_{n-1} \end{bmatrix}\),我们希望通过 \(fib(n-1)=\begin{bmatrix} f_{n-1} & f_{n-2} \end{bmatrix}\) 得到 \(fib(n)\),也就是说让 \(fib(n)=fib(n-1)×base\)

\(f_n=f_{n-1}+f_{n-2}\) 得:

转移矩阵的第一列为 \(\begin{bmatrix} 1 \\ 1 \end{bmatrix}\),因为 \(f_{n-1}×1+f_{n-2}×1=f_n\)

那么可以推出转移矩阵的第二列为 \(\begin{bmatrix} 1 \\ 0 \end{bmatrix}\),因为 \(f_{n-1}×1+f_{n-2}×0=f_{n-1}\)

那么转移矩阵就是 \(\begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}\)

因为 \(f_1=f_2=1\),那么设初始矩阵 \(fib(2)=\begin{bmatrix} 1 & 1 \end{bmatrix}\),乘一次 \(base\) 都能计算一项,也就是说我们要从 \(fib(2)\) 推到 \(fib(n)\) 需要乘 \(n-2\)\(base\)

也就说 \(A×base^{n-2}=B\)

核心代码如下:

inline void qpow (int b) {
    while (b) {
        if (b & 1) {
            Mul (f, f, a);///计算f=f×a。
        }
        
        Mul (a, a, a);//a=a^2。
        
        b >>= 1;
    }
}

最后输出 \(f_{0,0}\),答案矩阵的第一位就是 \(f_n\)

这里的矩阵快速幂仅仅是把快速幂中的数字改为了矩阵而已。


P1939 【模板】矩阵加速(数列)

设答案矩阵为 \(F(n)=\begin{bmatrix} a_{n} & a_{n-1} & a_{n-2} \end{bmatrix}\),有一个矩阵 \(F(n-1)=\begin{bmatrix} a_{n-1}&a_{n-1} & a_{n-3} \end{bmatrix}\),我们要构建矩阵 \(base\) 使 \(F(n-1)×base=F(n)\)

然后可以推出矩阵 \(base=\begin{bmatrix} 1 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{bmatrix}\)

套模板即可。


四.矩阵快速幂的运用

P1349 广义斐波那契数列

易得转移矩阵 \(base=\begin{bmatrix} p & 1 \\ q & 0 \end{bmatrix}\),初始矩阵 \(A=\begin{bmatrix} a_{2} & a_{1} \end{bmatrix}\),套矩阵快速幂模板转移 \(n-2\) 次即可。

P2044 [NOI2012] 随机数生成器

题目给出了递推式,发现要加上 \(c\),我们的初始矩阵就设为 \([x_0,c]\)

然后就根据递推式得出转移矩阵 \(\begin{bmatrix} a & 0 \\ 1 & 1 \end{bmatrix}\),矩阵快速幂 \(n\) 次转移即可。

但是我们发现数据非常大,那么就要使用龟速乘


Ybtoj【例题3】行为方案

P3758 [TJOI2017]可乐

P5789 [TJOI2017]可乐(数据加强版)

发现这个自爆操作不太好搞,可以创建一个虚拟点 \(0\),自爆操作就相当于走到 \(0\) 点。

可以设出一个状态 \(f_{i,j,k}\)\(i\) 走到 \(j\)\(k\) 步的方案数,那么就有一个暴力方程 \(f_{i,j,k}=\sum\) \(f_{i,p,k-1} × f_{p,j,1}\)

然后我们发现这个第三维可以压掉,那么 \(f_{i,j}=\sum f_{i,p}×f_{p,j}\)

这样的复杂度是 \(O(tn^2)\) 的,无法通过怎么办?

发现这个转移方程非常符合矩阵快速幂的形式,我们可以用矩阵快速幂优化到 \(O(\log tn^2)\)

最后的答案就是 \(\sum^{n}_{i=0} ans_{1,i}\)

代码如下:

#include <bits/stdc++.h>

using namespace std;

const int N = 105;
const int mod = 2017;

int n, m, t, base[N][N], ans[N][N];

inline void Mul (int f[N][N], int x[N][N], int y[N][N]) {
    int t[N][N];
    
    for (int i = 0; i <= n; i ++) {
        for (int j = 0; j <= n; j ++) {
            t[i][j] = 0;
        }
    } 
    
    for (int i = 0; i <= n; i ++) {
        for (int j = 0; j <= n; j ++) {
            for (int k = 0; k <= n; k ++) {
                t[i][j] = (t[i][j] + x[i][k] * y[k][j] % mod) % mod;
            }
        }
    }
    
    for (int i = 0; i <= n; i ++) {
        for (int j = 0; j <= n; j ++) {
            f[i][j] = t[i][j];
        }
    }
}

inline void qpow (int T) {
    while (T) {
        if (T & 1) {
            Mul (ans, ans, base);
        }
        
        Mul (base, base, base);
        
        T >>= 1;
    }
}

int main () {
    scanf ("%d%d", &n, &m);
    
    for (int i = 1; i <= m; i ++) {
        int u, v;
        
        scanf ("%d%d", &u, &v);
        
        base[u][v] = 1;
        base[v][u] = 1;//有连边的方案为1。
    }
    
    for (int i = 0; i <= n; i ++) {
        base[i][i] = 1;//自己到自己方案为1。
        base[i][0] = 1;//到爆炸点方案为1。
        ans[i][i] = 1;//初始矩阵设为单位矩阵。
    }
    
    scanf ("%d", &t);
    
    qpow (t);//因为初始的时刻为0,所以总共要转移t次。
    
    int sum = 0;
    
    for (int i = 0; i <= n; i ++) {
        sum = (sum + ans[1][i]) % mod;
    }
    
    printf ("%d", sum);
    
    return 0;
}

代码中初始矩阵和答案矩阵是同一个矩阵,因为我在快速幂时不断覆盖初始矩阵。

初始矩阵因为是没操作的情况,那么就设为单位矩阵。


Ybtoj【例题4】矩阵求和

这题我就稍微讲一下思路。(因为我也不太会做

我们构造矩阵 \(B=\begin{bmatrix} A & I \\ 0 & I \end{bmatrix}\),其中 \(A\) 为给出的初始矩阵, \(I\)\(n×n\) 的单位矩阵, \(0\)\(n×n\) 的零矩阵。

那么 \(B^{k+1}=\begin{bmatrix} A^{k+1} & I+\Sigma _{i=1}^{k}A^{i} \\ 0 & 1 \end{bmatrix}\),就发现 \(B^{k+1}\) 右上角的矩阵减去一个单位矩阵就是我们所要求的的答案。


Ybtoj【例题5】最短路径

P2886 [USACO07NOV]Cow Relays G

我们发现,有 \(100\) 条边,但有 \(1000\) 个点,那我们先离散化一下。

抛开边数限制,我们可以采用 \(Floyd\) 算法。

图的邻接矩阵存储本质上就是一个矩阵,那我们可以想到用矩阵快速幂优化。

由于矩阵乘法满足结合律,我们直接转化,将乘法变为取 \(\min\)

每次乘幂都是相当于走了一条边,那么乘 \(n\) 次转移矩阵就相当于走了 \(n\) 条边。

模板转化即可。

inline void Mul (int f[N][N], int x[N][N], int y[N][N]) {
    int t[N][N];
    for (int i = 0; i < tot; i ++) {
        for (int j = 0; j < tot; j ++) {
            t[i][j] = inf;//floyd算法初始为inf。
        }
    } 
    for (int i = 0; i < tot; i ++) {
        for (int j = 0; j < tot; j ++) {
            for (int k = 0; k < tot; k ++) {
                t[i][j] = min (t[i][j], x[i][k] + y[k][j]);//将乘法改为min。
            }
        }
    }
    for (int i = 0; i < tot; i ++) {
        for (int j = 0; j < tot; j ++) {
            f[i][j] = t[i][j];
        }
    }
}

完整代码。

多说一句,由于 \(Ybtoj\) 上的数据有问题,所以有一个点需要数据特判才能 \(AC\)


P2233 [HNOI2002]公交车路线

这种题一看就是线性递推,那么就容易想到用矩阵快速幂进行优化。

阅读题面先写出几个信息:

\(1.\) \(n\) 为偶数时方案数为 \(0\)

\(2.\) \(n<4\) 时方案数为 \(0\)

那么我们先特判 \(n\),然后把 \(n\) 除以二,这样每一个数字都是相邻的,便于处理。

我们可以马上秒一个 \(dfs\) 爆搜,得到前几位:
\(f_1=0,f_2=2,f_3=8,f_4=28\)

猜测 \(f_i\)\(f_{i-1}\)\(f_{i-2}\) 有关系,那我们列方程组,解得递推式如下:

\(f_i=4×f_{i-1}-2×f_{i-2}\)

设答案矩阵为 \(F(n)\),那么这样构建初始矩阵 \(F(2)=[0, 2]\),转移矩阵 \(base=\begin{bmatrix} 4 & 1 \\ -2 & 0 \end{bmatrix}\)

然后转移 \(n-2\) 次就可以了。

答案要乘 \(2\) ,因为我们只计算走向一边的方案。

又因为转移矩阵中有负数,所以我们要在答案输出时防止出现负数,像这样操作:
cout << (2 * ans[0][0] % mod + mod) % mod;

完整代码不放了就套模板即可。

posted @ 2022-01-23 22:20  cyhyyds  阅读(2307)  评论(0编辑  收藏  举报