# 演算法課程題解 - 動態規劃\: 轉移矩陣 # TOJ 416 ## 題目 https://toj.tfcis.org/oj/pro/416/ 你的魔力超級強大,可以輕鬆燒光敵軍, 但你的愛杖『超級智慧魔杖』(簡稱超智杖)卻承受不住你的全力。 你能施展火球術、超炎爆術、以及超大型魔法隕石術三種。 火球術不論施展幾次都沒問題,但超炎爆術只能連續使用兩次。 連續使用三次的話,超智杖就會過熱而毀損。 但只要中間隔一回合冷卻,就能夠再連續使用兩次。 而隕石術由於消耗魔力過大,對超智杖負荷太重,只能使用一次, 不管經過多少回合,只要再次使用,超智杖就會承受不住而折斷。 如果要施展 $n$ 回合的法術,你會有多少種不同的方式可以燒掉敵軍? ## 想法2 (100%) By Koios 這邊要改用轉移矩陣來找答案 我是第一次用轉移矩陣來解競賽題,所以詳細解釋一下 這裡以費氏序列舉例 $$ f(x) = f(x-1) + f(x-2) $$ 改用矩陣的方式來表達的話,可以寫成 $$ f(x) = \begin{bmatrix} f(x-1) & f(x-2)\end{bmatrix} \begin{bmatrix} 1 \\ 1 \end{bmatrix} $$ 接下來嘗試讓結果也變成形如 $\begin{bmatrix} f(x-1) & f(x-2)\end{bmatrix}$ 的樣子,這樣我們之後就可以通用這個矩陣來求下一項的答案 $$ \begin{bmatrix} f(x) & f(x-1)\end{bmatrix} = \begin{bmatrix} f(x-1) & f(x-2)\end{bmatrix} \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} $$ 也就是說 $$ \begin{bmatrix} f(1) & f(0)\end{bmatrix} \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} ^{n} = \begin{bmatrix} f(1+n) & f(n)\end{bmatrix} $$ 回到這一題,我們總共有 $6$ 種狀態,分別從 $dp[i][0][0]$ 到 $dp[i][2][1]$,可以改寫成矩陣的樣子 $$ \begin{bmatrix} f(i+1, 0, 0) \\ f(i+1, 0, 1) \\ f(i+1, 1, 0) \\ f(i+1, 1, 1) \\ f(i+1, 2, 0) \\ f(i+1, 2, 1) \end{bmatrix} = \begin{bmatrix} 1 & 0 & 1 & 0 & 1 & 0 \\ 1 & 1 & 1 & 1 & 1 & 1 \\ 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 & 0 \end{bmatrix} \begin{bmatrix} f(i, 0, 0) \\ f(i, 0, 1) \\ f(i, 1, 0) \\ f(i, 1, 1) \\ f(i, 2, 0) \\ f(i, 2, 1) \end{bmatrix} $$ 也就是說 $$ \begin{bmatrix} f(n+1, 0, 0) \\ f(n+1, 0, 1) \\ f(n+1, 1, 0) \\ f(n+1, 1, 1) \\ f(n+1, 2, 0) \\ f(n+1, 2, 1) \end{bmatrix} = \begin{bmatrix} 1 & 0 & 1 & 0 & 1 & 0 \\ 1 & 1 & 1 & 1 & 1 & 1 \\ 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 & 0 \end{bmatrix}^{n} \begin{bmatrix} f(0, 0, 0) \\ f(0, 0, 1) \\ f(0, 1, 0) \\ f(0, 1, 1) \\ f(0, 2, 0) \\ f(0, 2, 1) \end{bmatrix} $$ 令 $A = \begin{bmatrix} 1 & 0 & 1 & 0 & 1 & 0 \\ 1 & 1 & 1 & 1 & 1 & 1 \\ 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 & 0 \end{bmatrix}$ 接下來用到矩陣快速冪來快速算出 $A^n$ 概念跟快速冪相同 遇到次方 $n$ 是偶數,就先算 $n/2$ 次方的答案,再把該答案平方就會是整體的答案 反之次方 $n$ 是奇數,就算出 $n-1$ 次方的答案,再跟 $A$ 相乘一次就會是整體的答案 不過需要實作一下矩陣乘法運算 ### 程式碼 ```cpp= //By Koios1143 #include<iostream> using namespace std; const long long Mod = 1000000007; int n; long long A[6][6] = { {1, 0, 1, 0, 1, 0}, {1, 1, 1, 1, 1, 1}, {1, 0, 0, 0, 0, 0}, {0, 1, 0, 0, 0, 0}, {0, 0, 1, 0, 0, 0}, {0, 0, 0, 1, 0, 0}}; long long M[6] = {1, 1, 1, 0, 0, 0}; // (6x6) x (6x6) 矩陣乘法 void mul(long long (*p)[6], long long (*q)[6], long long (*res)[6], long long Mod){ for(int i=0 ; i<6 ; i++){ for(int j=0 ; j<6 ; j++){ res[i][j] = 0; for(int k=0 ; k<6 ; k++){ res[i][j] += p[i][k] * q[k][j]; res[i][j] %= Mod; } } } } // (6x6) x (6x1) 矩陣乘法 void mul2(long long (*p)[6], long long *q, long long *res, long long Mod){ for(int i=0 ; i<6 ; i++){ res[i] = 0; for(int j=0 ; j<6 ; j++){ res[i] += p[i][j] * q[j]; res[i] %= Mod; } } } // (6x6) x (6x6) 矩陣快速冪 void qpow(long long (*p)[6], long long q, long long (*res)[6], long long Mod){ long long tmp[6][6]; if(q == 1){ for(int i=0 ; i<6 ; i++){ for(int j=0 ; j<6 ; j++){ res[i][j] = p[i][j]; } } return; } if(q % 2 == 0){ qpow(p, q/2, tmp, Mod); mul(tmp, tmp, res, Mod); } else{ qpow(p, q-1, tmp, Mod); mul(p, tmp, res, Mod); } } int main(){ while(cin>>n){ // 避免詢問 n-1 時為 0 if(n == 1){ cout<<3<<"\n"; continue; } long long res[6][6], res2[6], ans = 0; // 計算 A 的 n-1 次方 qpow(A, n-1, res, Mod); // 再跟原矩陣相乘 mul2(res, M, res2, Mod); for(int i=0 ; i<6 ; i++){ ans += res2[i]; ans %= Mod; } cout<<ans<<"\n"; } return 0; } ``` ### 時間複雜度分析 一次矩陣乘法所需的操作數量約為 $6 \times 6 \times 6$ 矩陣快速冪操作次數約為 $log_{2}n$,每次會做一次矩陣乘法,總操作數約為 $216log_{2}n$,計為 $O(log_{2}n)$ 最後跟原矩陣相乘所需的操作數量約為 $6 \times 6 \times 1$,計為 $O(1)$ 每筆測資總時間複雜度約為 $O(log_{2}n)$ ###### tags: `SCIST 演算法 題解`