文章目錄
一、快速冪
快速冪:快速計算某個數的冪次( )
快速冪時間複雜度爲 ,相比樸素的 快了很多
它的基本原理是把 n 轉換爲二進制,如
核心:反覆平方法,即 不斷把底數平方
快速冪模版(迭代,非遞歸)
int fastpow(int a, int k, int p) {
int res = 1;
while (k) {
if (k & 1) res = res * 1ll * a % p;
a = a * 1ll * a % p;
k >>= 1;
}
return res;
}
快速冪模版(遞歸)
計算 a 的 k 次,如果 k 爲偶數(不爲0),則先計算 ,然後平方;如果 k 是奇數,則先計算 ,然後在乘以 a;遞歸的出口是
int fastpow(int a, int k, int p) {
if (k == 0) return 1 % p;
if (k & 1) return fastpow(a, k - 1, p) * 1ll * a % p;
else {
// 必須用臨時遍歷tmp存,否則算法退回到O(n)
int tmp = fastpow(a, k / 2, p);
return tmp * 1ll * tmp % p;
}
}
說明:遞歸快速冪思路比較簡單,但要注意 需要用臨時遍歷存下來,否則算兩次,會讓算法退回到 複雜度
注:快速冪由於過於簡單,通常作爲某個複雜算法的中間一步,比如歐拉公式,歐幾里得算法
AcWing 875. 快速冪
LeetCode 50. Pow(x, n)(快速冪 C++)
LeetCode 題解 | 50. Pow(x, n)(快速冪 C++)
LeetCode 372. 超級次方
LeetCode 題解 | 372. 超級次方(快速冪 C++)
AcWing 876. 快速冪求逆元(乘法逆元)
乘法逆元定義: ,當 b 和 p 互質時,方程的解 x 稱爲 b 模 p 的逆元
說明:乘法逆元不唯一, 是通解
求同餘方程 ,即求 a 關於 模 p 的逆元
這裏模 p 的 p 是質數,是特殊情況,所以可以用快速冪來求,更一般的做法是用擴展歐幾里得算法,構造出 x
/* ===== 解題思路 =====
1. p是質數,當 a 和 p 互質時,a 模 p 的乘法逆元是 a^(p-2) % p (在0~p-1上的逆元)
2. 用快速冪求 a^(p-2) % p
*/
#include <iostream>
using namespace std;
int fastpow(int a, int k, int p) {
int res = 1;
while (k) {
if (k & 1) res = res * 1ll * a % p;
a = a * 1ll * a % p;
k >>= 1;
}
return res;
}
int main() {
int n;
cin >> n;
while (n --) {
int a, p;
cin >> a >> p;
if (a % p != 0) { // a 和 p 互質
cout << fastpow(a, p - 2, p) << endl;
} else {
cout << "impossible" << endl;
}
}
return 0;
}
二、矩陣快速冪
數學問題:矩陣n次方的七種求法的歸納——在本科數學時學過裏面的幾種方法
上面說的是數學方法,在計算機編程中經常用快速冪來求矩陣的 n 次冪
矩陣快速冪適合於求遞推式 ,加速遞推公式
特別是當 n 很大時,它能在 級別的時間複雜度內求出第 n 項,如求斐波那契數列的第1e9項
顯然當我們算出 後, 的值就都算出來了(這算法是不是很牛逼!)
我們用一個結構體來封裝矩陣,這樣可以簡化很多代碼,讓程序看上去更舒服
struct mat{
int m[N][N];
mat() { // 用構造函數初始化成 單位矩陣E
memset(m, 0, sizeof m);
for (int i = 0; i < N; i ++) m[i][i] = 1;
}
}
矩陣快速冪函數(和普通快速冪類似)
mat fastpow(mat a, int k) { // 矩陣快速冪
mat res;
while (k) {
if (k & 1) res = multi(res, a);
a = multi(a, a);
k >>= 1;
}
return res;
}
矩陣快速冪模版
結構體(存矩陣) + 矩陣乘法 + 矩陣快速冪
#include <iostream>
using namespace std;
const int N = 100, mod = 1e6;
int n, k;
struct mat {
int m[N][N];
mat() { // 用構造函數初始化成 單位矩陣E
memset(m, 0, sizeof m);
for (int i = 0; i < N; i++)
m[i][i] = 1;
}
};
// 矩陣乘法
mat multi(mat a, mat b) {
mat c;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
c.m[i][j] = 0;
for (int k = 0; k < n; k++) {
c.m[i][j] += a.m[i][k] * b.m[k][j]; // 累加
}
c.m[i][j] %= mod;
}
}
return c;
}
// 矩陣快速冪(核心)
mat fastpow(mat a, int k) {
mat res;
while (k) {
if (k & 1) res = multi(res, a);
a = multi(a, a);
k >>= 1;
}
return res;
}
int main() {
cin >> n >> k; // 輸入矩陣階數 n 和冪次 k
mat a, res;
// 輸入矩陣
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cin >> a.m[i][j];
}
}
res = fastpow(a, k);
// 輸出結果
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cout << res.m[i][j] << " ";
}
cout << endl;
}
}
輸入
3 10
-1 1 0
-4 3 0
1 0 2
輸出
-19 10 0
-40 21 0
-1003 1013 1024
將 n=10 代入通項 ,可以知道我們的代碼是正確的
說明:一般題目給的數據會很大,所以都會取 mod
例題:求斐波那契數列的第 1e9 項
POJ 題解 | 3070.Fibonacci 求斐波那契數列的第 n 項(矩陣快速冪 C++)
例題:
分析可以得到
k爲偶數:
k爲奇數:
可以用矩陣快速冪來求,然後再遞歸 sum 即可
POJ 題解 | 3233. Matrix Power Series(矩陣快速冪 C++)
@例題:商湯科技筆試第三題
關鍵在於構造出遞推的矩陣
可以理解爲選定一組基
@數位問題
在所有的 n 位正數中,有多少個數含有偶數個3,有多少個數含有奇數個3?
注:
參考:https://zhuanlan.zhihu.com/p/137677246
三、快速乘
由於計算機底層設計的原因,做加法往往比乘法快的多,因此將乘法轉換爲加法計算將會大大提高乘法運算的速度
除此之外,當我們計算 的時候,往往較大的數計算 會超出 long long int 的範圍,這個時候使用快速乘法方法也能解決上述問題。
快速乘法的原理:利用乘法分配率來將轉化爲多個式子相加的形式求解
例如:
typedef long long ll;
ll qmm(ll a, ll b, ll mod) {
ll ans = 0;
while (b) {
if (b & 1) ans = (ans + a) % mod;
a <<= 1; // 20*1->20*2->20*4->20*8
b >>= 1;
}
return ans;
}
參考資料
[1] 數論之矩陣快速冪
[2] 算法學習筆記(4):快速冪 - 知乎
[3] 算法競賽模板 快速乘與快速冪 - Kannyi - 博客園
[4] 二分冪,快速冪,矩陣快速冪,快速乘