# 遞迴 函式自己呼叫自己,並存放於stack中(因為是先進後出) ## 費氏數列 $$f(n)= \begin{cases} f(n-1)+f(n-2) & n\geq3\\ \quad\quad\quad\quad 1 & n=1,2 \end{cases}$$ 寫成遞迴式 ```cpp int f(int n){ if(n<=2) return 1; return f(n-1)+f(n-2); } ``` ### 優化 我們可以由上式推得: $$ f(n)=\frac{(\frac{\sqrt{5}+1}{2})^n-{(\frac{\sqrt{5}-1}{2})^n}}{\sqrt{5}} $$ 那麼這個遞迴式的複雜度為$O(2^n)$,再觀察遞迴樹: ![](https://hackmd.io/_uploads/r1Qgk8axa.png) 發現一個函式可能被呼叫多次,那麼如果把執行過一次的函式記錄起來,下次呼叫就可以$O(1)$回答,此時複雜度為$O(n)$ ```cpp vector<int> fib(100000); int f(int n){ if(fib[n]) return fib[n]; if(n<=2) fib[n]=1; else fib[n]=f(n-1)+f(n-2); return fib[n]; } ``` 另外,減少參數的數量(如使用全域變數)可以提升效率 ### 補充:矩陣優化 #### 快速冪 欲求$x^n$,暴力乘一遍,$O(n)$ 若把$n$拆為2的次方,ex: $x^{11}=x^8 \times x^2 \times x^1$ 事先求出$x^1,x^2,x^4...$只需$O(logn)$(一直平方到超過n就好了) 又$n$可由2的次方組合而成(並且總數不超過$log_2n$),所以複雜度可達$O(logn)$ 為求簡潔,一邊求$x^{2^n}$一邊組合可寫成: ```cpp int muti(int x,int n){ int ans=1; while(n>0){ if(k%2) ans*=x; x*=x; n>>=1; } return ans; } ``` #### 矩陣 一個$2 \times 3$的矩陣A表示如下: $$A= \begin{bmatrix} a_{1,1} & a_{1,2} & a_{1,3}\\ a_{2,1} & a_{2,2} &a_{2,3} \end{bmatrix}$$ 若A為 $x \times y$ 的矩陣,B為 $y \times z$ 的矩陣,則定 $A \times B$ 為 $x \times z$ 的矩陣C表示如下: $$c_{i,j}=\sum_{k=1}^ya_{i,k}+b_{k,j}$$ #### 費氏數列 因為$fib(n)=fib(n-1)+fib(n-2)$,所以可得 $$ \begin{bmatrix} fib(n-1) & fib(n-2) \end{bmatrix} \times \begin{bmatrix} 1 & 1 \\ 1 & 0\end{bmatrix}= \begin{bmatrix} fib(n) & fib(n-1) \end{bmatrix}$$ 推成一般式: $$ \begin{bmatrix} 1 & 1 \end{bmatrix} \times \begin{bmatrix} 1 & 1 \\ 1 & 0\end{bmatrix}^{n-2}= \begin{bmatrix} fib(n) & fib(n-1) \end{bmatrix}$$ 因為矩陣乘法有結合率,所以可以用快速冪! 這樣一來求$fib(n)$的複雜度就是$O(logn)$ ## 剪枝 遞迴可以模擬所有情況,再從中找出(或枚舉出)答案,但是對於已經不可能是答案的情況,就不用繼續遞迴下去 ### 括號匹配問題 [zj:a229](https://zerojudge.tw/ShowProblem?problemid=a229) 列出合法的括號(左邊一個配右邊一個),$n=$括號組數$\leq13$ #### 題解 考慮暴力遞迴,每次選左和選右,如果長度$=2n$就停止,檢查是否合法,複雜度$O(2^{2n})$,且$n\leq13$,最多會執行$10^{7.8}$次,顯然過不了。 那麼有什麼情況下可以提前終止呢?如果一種括號已經超過n個,那麼顯然不可能成對:又如果右括號出現的比較多次也不可能合法。 ![](https://hackmd.io/_uploads/Hk5bPL6eT.png) [圖源:上一屆的講義](https://hackmd.io/@niter/ryhPGrsBs) ### Grid Paths [cses:Grid Paths](https://cses.fi/problemset/task/1625/) 從左上到左下走滿7*7方格 ![](https://hackmd.io/_uploads/HkzstLaeT.png) 因為總步數恆等於48,所以走到48步時檢查是否在左下方格就可以得知是不是解 #### 剪枝1 顯然,如果走到已經走過的格子就可以不用走了 #### 剪枝2 撞到牆也可以不用走了 #### 剪枝3 如果提早走到終點也不可能是解 剪到這邊發現還是TLE,只好繼續剪... #### 剪枝4 ![](https://hackmd.io/_uploads/S1WX6Iaga.png) 如果前面是牆,左右都沒走過,那麼左右一定有一個走不到(地圖被分成兩半) #### 剪枝5 ![](https://hackmd.io/_uploads/HJm7AL6g6.png) 如果右上有走過,而上和右都沒走過,那麼上右一定有一個走不到(有一個被包圍了),轉向後同理,可以剪四種 至此,我們知道了剪枝後的複雜度很難算(至少我算不出來),但是肯定比原本快。如果還是TLE的話代表剪不夠,還有你沒想到的情況可以剪