基基基基礎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)| = 2lcs(ace,cae)=2

|lcs(abc,def)| = 0lcs(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

題目

Made with Slides.com