基基基基礎DP
先來看個栗子
東東有個嗜好,爬階梯不是一次走一階,就是一次走兩階。
換句話說,假設階梯有三階,那他有三種走法
一:第一步走一階,第二步走二階。
二:第一步走二階,第二步走一階。
三:全程都走一階。
這題要問你,假設階梯有\(n(0<n<100)\)階,那東東有幾種走法?
到第\(1\)階有\(1\)種走法
到第\(2\)階有\(2\)種走法
到第\(n\)階有\(f(n-1) + f(n-2)\)種走法
其實是費式數列啦 :D
那我們就殼以愉快ㄉ用遞迴寫......嗎?
#include <bits/stdc++.h>
using namespace std;
long long f(int n){
if(n == 1) return 1;
else if(n == 2) return 2;
return f(n-1) + f(n-2);
}
int main(){
int n;
cin >> n;
cout << f(n) << '\n';
return 0;
}
扣ㄉ
但這樣太慢ㄌ
複雜度?
\(f_2\)
\(f_5\)
\(f_2\)
\(f_3\)
\(f_2\)
\(f_3\)
\(f_3\)
\(f_4\)
\(f_4\)
\(f_1\)
\(f_2\)
\(f_1\)
\(f_6\)
\(f_2\)
\(f_1\)
複雜度?
每個\(f(n)\)會拆成\(f(n-1)\)和\(f(n-2)\)算
所以每次都拆成兩個
複雜度\(O(2^n)\)
怎麼辦?
把算過的記下來!
把算過的記下來
開一個陣列記錄
遇到算過的就直接用,這樣每一項就只會算到一次
複雜度\(O(n)\)
複雜度?
\(f_2\)
\(f_5\)
\(f_2\)
\(f_3\)
\(f_2\)
\(f_3\)
\(f_3\)
\(f_4\)
\(f_4\)
\(f_1\)
\(f_2\)
\(f_1\)
\(f_6\)
\(f_2\)
\(f_1\)
code
#include <bits/stdc++.h>
using namespace std;
long long dp[85];
long long f(int n){
if(dp[n]) return dp[n];
if(n <= 2) return dp[n] = n;
return dp[n] = f(n-1) + f(n-2);
}
signed main(){
int n;
cin >> n;
cout << f(n) << '\n';
}
top down
#include <bits/stdc++.h>
using namespace std;
long long dp[85];
signed main(){
int n;
cin >> n;
dp[1] = 1;
dp[2] = 2;
for(int i=3; i<=n; ++i)
dp[i] = dp[i-1] + dp[i-2];
cout << dp[n] << '\n';
}
bottom up
所以什麼是dp
- 動態規劃
- 把大問題分成小問題,再用小問題湊出大問題的答案
-
通常能使用DP的問題 會有「重疊子問題」、「最優子結構」的性質。
以下不重要
重疊子問題:有些子問題會被重複計算多次
最優子結構:如果一個問題的解,拿去對應到的子問題也是最佳的,那我們就說這個問題有最優子結構特性。
dp的步驟
- 定義狀態
- 推轉移式
- 打好邊界
時間複雜度通常是 O(O( 狀態數 \times× 狀態轉移花費 ))
搭配剛剛的栗子
- 定義狀態
- 推轉移式
- 打好邊界
時間複雜度是 O(O( 狀態數 \times× 狀態轉移花費 )) = \(O(n)\)
\(dp_i\)是到第\(i\)階的方法數
\(dp_1=1,\ dp_2=2\)
\(dp_i = dp_{i-1} + dp_{i-2}\)
好ㄉ那我們今天要開始ㄌ
最大連續和
已知一\(\ n(1 \le n \le 100)\)個元素的整數數列\(A\),找出該數列連續元素的和的最大值。
\(1\le n \le 100\)
\(-10000\le A_i \le 10000\)
e.g. 10 -5 7 6 -1 -3
max = 10 + (-5) + 7 + 6 = 18
想一下la (其實這滿greedyㄉ
最大連續和
定義狀態:
狀態轉移式:
邊界:
定義\(dp_i\) 是以第\(i\)項為結尾的最大連續和
\(dp_i = max(dp_{i-1}, 0) + a_i\)
如果以第\(i-1\)項為結尾的最大連續和是負的就不要取,反之則加上去
\(dp_0 = 0\)
題目
Path on Grid
給你一個 \(N \times M\) 的棋盤
令左上角為 \((1,1)\) ,右下角為 \((N,M)\)
如果從 \((1,1)\) 開始,只能往右或往下走
試問有幾種相異的走法可以走到 \((N,M)\)
請將答案模 \(10^9 + 7\) 輸出
e.g. n=2 m=3有3種走法
定義狀態:
狀態轉移式:
邊界:
定義\(dp[i][j]\) 是走到\((i, j)\)的方法數
因為只能往下走或往右走,所以我一定是從上或是左邊走來的
\(dp[i][j] = dp[i-1][j] + dp[i][j-1]\)
\(dp[1][1] = 1\)
因為走到\((1,1)\)的方法數只有\(1\)種
要怎麼模\(10^9+7\) ?
最後模?
在算到最後之前已經爆long long ㄌ
同餘定理!
同餘定理
- \((a+b) \% \ c = ((a \% c)+(b \% c)) \% c\)
e.g. \((7 + 9) \% 5 = ((7 \% 5) + (9\%5)) \% 5= (2 + 4) \% 5 = 1\)
- \((a \times b) \% c = ((a\%c) \times (b\%c)) \% c\)
e.g. \(3^{10} \% 4 = 9^5 \% 4 = (9\%4)^5 \% 4 = 1\)
題目
一個字串\(A\)和一個字串\(B\),找最長的一樣的子序列長度
e.g.
∣lcs(abcd,bc)∣=2
|lcs(ace,cae)| = 2∣lcs(ace,cae)∣=2
|lcs(abc,def)| = 0∣lcs(abc,def)∣=0
定義狀態:
狀態轉移式:
定義\(dp[i][j]\) 是字串\(A\)的前\(i\)項和字串\(B\)的前\(j\)項的LCS長度
分成三種狀況
LCS
狀態轉移式:
分成4種狀況
\(A_i\)和\(B_j\)都是LCS的一部份,\(dp[i][j]=dp[i-1][j-1]+1\)
\(A_i\)是LCS的一部份,而\(B_j\)不是,\(dp[i][j] = dp[i][j-1]\)
\(A_i\)不是LCS的一部份,但\(B_j\)是,\(dp[i][j] = dp[i-1][j]\)
\(A_i\)和\(B_j\)都不是LCS的一部份,\(dp[i][j] = dp[i-1][j-1]\)
LCS
狀態轉移式:
if \(A_i = B_j \ ,dp[i][j] = dp[i-1][j-1]+1\)
if \(A_i \neq B_j \ , dp[i][j] = max(dp[i-1][j], dp[i][j-1])\)
LCS
狀態轉移式:
if \(A_i = B_j \ ,dp[i][j] = dp[i-1][j-1]+1\)
if \(A_i \neq B_j \ , dp[i][j] = max(dp[i-1][j], dp[i][j-1])\)
LCS
題目
基礎DP
By mouyilai
基礎DP
- 419