矩阵快速幂与矩阵方法
/ / 阅读耗时 6 分钟 本节介绍矩阵快速幂以及矩阵在ACM中的简单应用。本文需要一些线性代数知识作铺垫,这里不再重复。关于快速幂的思想,建议先阅读快速幂。
首先看这么一道题(洛谷P1307):
题目描述
对于Fibonacci数列:1,1,2,3,5,8,13……大家应该很熟悉吧~~~但是现在有一个很“简单”问题:第n项和第m项的最大公约数是多少?
输入输出格式
输入格式:
两个正整数n和m。(n,m≤109)
注意:数据很大
输出格式:
Fn和Fm的最大公约数。
由于看了大数字就头晕,所以只要输出最后的8位数字就可以了。
输入输出样例
Sample input
4 7
Sample output
1
要解决本题需要先认识到一个事实(证明略):
于是只需要求出n与m的最大公因数,再找它所对应的一位即可。注意在整个过程中对1e8取模,时间复杂度O(gcd(n,m))。
但是,对于很大的数(比如本题达到1e9的数量级),循环1e9次显然会超时。于是需要借助矩阵这个工具来简化运算过程。
矩阵是解决线性问题的利器。所谓运用矩阵工具,就是要善于发现题目中的线性关系。注意到斐波那契数列的递推式为:
这是一个典型的线性关系,立即可以得到(用f(x)表示Fib(x)):
发现两侧的非常数矩阵形式不同,不利于递推,于是把它扩展为:
进行如下定义:
那么有递推关系:
这就是斐波那契数列的矩阵递推式,反复利用递推公式可以得到:
这就是斐波那契数列的矩阵通项公式,问题转化为如何快速求矩阵的幂。结合整数快速幂的思想,仍然可以用倍增方法求解矩阵的幂,将求矩阵乘积次数降至对数级别。
矩阵快速幂与整数快速幂有以下几点区别:
- 矩阵的平方和乘积更复杂,可以采用朴素的行乘列法则暴力求出。
- 初始化的值由1改为单位矩阵,即:
利用矩阵快速幂优化,本题迎刃而解。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using namespace std;
struct Matrix {//2*2矩阵
long long Main[2][2]{};
Matrix(int a, int b, int c, int d) {
Main[0][0] = a, Main[0][1] = b, Main[1][0] = c, Main[1][1] = d;
}
void operator*=(Matrix x) {//重载乘法运算
long long temp[2][2];
temp[0][0] = (Main[0][0] * x.Main[0][0] + Main[0][1] * x.Main[1][0]) % MOD;
temp[0][1] = (Main[0][0] * x.Main[0][1] + Main[0][1] * x.Main[1][1]) % MOD;
temp[1][0] = (Main[1][0] * x.Main[0][0] + Main[1][1] * x.Main[1][0]) % MOD;
temp[1][1] = (Main[1][0] * x.Main[0][1] + Main[1][1] * x.Main[1][1]) % MOD;
Main[0][0] = temp[0][0], Main[0][1] = temp[0][1];
Main[1][0] = temp[1][0], Main[1][1] = temp[1][1];
}
};
int gcd(int a, int b) {
if (a < b)swap(a, b);
if (b == 0)return a;
return gcd(b, a % b);
}
int main() {
int n, m;
cin >> n >> m;
Matrix sta(1, 1, 1, 0), ans(1, 0, 0, 1);//ans为单位阵
int p = gcd(n, m) - 1;//下计算sta的p次方,矩阵快速幂
while (p > 0) {
if ((p & 1) == 1)ans *= sta;
sta *= sta;
p >>= 1;
}
cout << (ans.Main[1][0] + ans.Main[1][1]) % MOD;
return 0;
}