快速幂

快速幂

例题:P1226 【模板】快速幂||取余运算 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

问题:对于一个数a,我们如何求出他的b次方?由于数可能比较大我们需要对结果对p取模

一般做法是a*a*a*…*a,一共有b个a相乘,如果b足够大,那么时间复杂度将为O(b),那么有没有什么方法可以快速运算呢?

例如:求2^10

我们需要先进行一个降幂操作,先使最终结果为sum,sum初始化为1

base 2 4 4 16 32 32
power 10 5 4 2 1 0
now 2^10 4^5 4^4 16^2 32^1 32^0
sum 1 1 1*4=4 4 4 4*32=128=2^10
times 0 1 1 2 3 3

由上表可以看出,当power为奇数的时候,我们需要让sum*=base,同时power-=1

当power==0时,此时sum就是最终结果

由例子可以看出,如果用暴力算法,需要运算10次,但是如果用快速幂只需要运算3次,故时间复杂度为O(logN)

如果我们要让结果对于p取模,根据取模的性质,我们只需每次操作的时候对sum和base取模即可

代码

1
2
3
4
5
6
7
8
9
10
11
long long fast(ll base, ll power, ll mod) {
ll sum = 1;
while (power > 0) {
if (power % 2 == 1) {
sum = (sum * base) % mod;
}
base = (base * base) % mod;
power /= 2;
}
return sum % mod;
}

优化

对于2^10,我们可以发现:

10转化为二进制之后为:1010

第一次操作变为5 : 101 -> sum*=base

第二次操作变为2 : 10

第三次操作变为1 : 1 -> sum*=base

所以我们可以通过位运算对其进行简化

1
2
3
4
5
6
7
8
9
10
11
ll fast(ll base, ll power, ll mod) {
ll sum = 1;
while (power > 0) {
if (power & 1) {
sum = (sum * base) % mod;
}
base = (base * base) % mod;
power >>= 1;
}
return sum % mod;
}

矩阵快速幂

首先你要会矩阵乘法,如果不会的话去CSDN - 专业开发者社区查,这里不多说,直接附上代码以及题目

(90条消息) 【详解】矩阵乘法__BOSS_的博客-CSDN博客_矩阵乘法

题目详情 - L1-048 矩阵A乘以B (pintia.cn)

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
43
44
45
46
47
#include <bits/stdc++.h>
#include <string>
using namespace std;
typedef long long ll;
ll a[105][105], b[105][105];
ll c[105][105];
void mul(int r1, int r, int c2) {
memset(c, 0, sizeof c);
for (int i = 0; i < r1; i++) {
for (int j = 0; j < c2; j++) {
for (int k = 0; k < r; k++) {
c[i][j] += a[i][k] * b[k][j];
}
}
}
}
int main() {
int r1, c1, r2, c2;
cin >> r1 >> c1;
for (int i = 0; i < r1; i++) {
for (int j = 0; j < c1; j++) {
cin >> a[i][j];
}
}
cin >> r2 >> c2;
if (c1 != r2) {
printf("Error: %d != %d", c1, r2);
return 0;
}
for (int i = 0; i < r2; i++) {
for (int j = 0; j < c2; j++) {
cin >> b[i][j];
}
}
mul(r1, c1, c2);
cout << r1 << ' ' << c2 << endl;
for (int i = 0; i < r1; i++) {
for (int j = 0; j < c2; j++) {
if (j) {
cout << ' ';
}
cout << c[i][j];
}
cout << endl;
}
return 0;
}

会了矩阵乘法之后,我们依旧用快速幂的思路来处理矩阵快速幂

这里附上题目链接P3390 【模板】矩阵快速幂 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

1
2
3
4
5
6
7
8
9
10
void fast(ll base, ll power, ll mod) {
单位矩阵 sum;
while (power > 0) {
if (power & 1) {
矩阵乘法1:sum = sum * base;
}
矩阵乘法2:base = base * base;
power >>= 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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
ll n, k;
ll a[105][105];
ll ans[105][105];
void get1() {
ll c[105][105];
memset(c, 0, sizeof c);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
c[i][j] += (ans[i][k] * a[k][j]) % mod;
c[i][j] %= mod;
}
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
ans[i][j] = c[i][j];
}
}
}
void get2() {
ll c[105][105];
memset(c, 0, sizeof c);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
c[i][j] += (a[i][k] * a[k][j]) % mod;
c[i][j] %= mod;
}
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
a[i][j] = c[i][j];
}
}
}
void fast(ll power) {
while (power > 0) {
if (power & 1) {
get1();
}
get2();
power >>= 1;
}
}
int main() {
cin >> n >> k;
for (int i = 0; i < n; i++) {
ans[i][i] = 1;
for (int j = 0; j < n; j++) {
cin >> a[i][j];
}
}
fast(k);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (j) {
cout << ' ';
}
cout << ans[i][j];
}
cout << endl;
}
return 0;
}

矩阵快速幂与斐波那契数列

题目:P1962 斐波那契数列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

斐波那契数列应该都不陌生,它的每一项都是前两项之和,即:
$$
F_n = F_{n-1} + F_{n-2}\
其中F_0 = F_1 = 1
$$
我们将它写成
$$
\left[\begin{matrix}
F_n\
F_{n-1}
\end{matrix}\right]
$$
我们可以将其推到成:
$$
\left[\begin{matrix}
F_n\
F_{n-1}
\end{matrix}\right]=
\left[\begin{matrix}
1 & 1\
1 & 0
\end{matrix}\right]
*
\left[\begin{matrix}
F_{n-1}\
F_{n-2}
\end{matrix}\right]
$$
同理,我们不断对其进行拆分可得:
$$
\left[\begin{matrix}
F_n\
F_{n-1}
\end{matrix}\right]=
{\left[\begin{matrix}
1 & 1\
1 & 0
\end{matrix}\right]}^{n-1}
*
\left[\begin{matrix}
F_1\
F_0
\end{matrix}\right]
$$
结合以上可以得到完整代码:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
ll n, k;
ll a[2][2];
ll ans[2][2];
ll b[2][1];
void get1() {
ll c[105][105];
memset(c, 0, sizeof c);
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
for (int k = 0; k < 2; k++) {
c[i][j] += (ans[i][k] * a[k][j]) % mod;
c[i][j] %= mod;
}
}
}
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
ans[i][j] = c[i][j];
}
}
}
void get2() {
ll c[105][105];
memset(c, 0, sizeof c);
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
for (int k = 0; k < 2; k++) {
c[i][j] += (a[i][k] * a[k][j]) % mod;
c[i][j] %= mod;
}
}
}
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
a[i][j] = c[i][j];
}
}
}
void fast(ll power) {
while (power > 0) {
if (power & 1) {
get1();
}
get2();
power >>= 1;
}
}
ll c[2][2];
void mul() {
memset(c, 0, sizeof c);
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 1; j++) {
for (int k = 0; k < 2; k++) {
c[i][j] += (ans[i][k] * b[k][j]) % mod;
c[i][j] %= mod;
}
}
}
}
int main() {
cin >> n;
a[0][0] = a[0][1] = a[1][0] = 1;
ans[0][0] = ans[1][1] = 1;
b[0][0] = 1;
fast(n - 1);
mul();
cout << c[0][0];
return 0;
}