本篇博客为一些动态规划学习的笔记。
前言
一个问题能用动态规划解决需要满足三个条件:有边界条件、有递推方程且具有最优子结构性质。
递推地求解斐波那契数列是最朴素的动态规划问题之一,下面三个函数的求解效率从上到下依次递增。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
   | #include <iostream> using namespace std;
 
  const int N = 30;
  int n, mem[N];
 
  int fib_by_dfs(int n) { 	if (n == 0 || n == 1) return 1; 	else return fib_by_dfs(n - 1) + fib_by_dfs(n - 2); }
 
  int fib_by_dfs_mem_opt(int n) { 	if (~mem[n]) return mem[n]; 	else return mem[n] = (mem[n - 1] = fib_by_dfs(n - 1)) + 		(mem[n - 2] = fib_by_dfs(n - 2)); }
 
  int fib_by_dp_opt(int n) { 	int dp[N]; 	dp[0] = 1, dp[1] = 1; 	for (int i = 2; i <= n; i++) { 		dp[i] = dp[i - 1] + dp[i - 2]; 	} 	return dp[n]; }
  int main() { 	int n; 	cin >> n; 	 	memset(mem, -1, sizeof mem); 	cout << fib_by_dp_opt(n) << endl; 	return 0; }
   | 
 
从上述例子不难看出,动态规划算法是对暴力搜索算法的优化。下面再以最低通行费为例,不难写出如下的暴力搜索算法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | #include <iostream> using namespace std;
  const int N = 104;
  int g[N][N], n;
 
  int dfs(int x, int y) {     if(x == n && y == n) return g[n][n];     if(x > n || y > n) return 1e9;     return min(dfs(x + 1, y), dfs(x, y + 1)) + g[x][y]; }
  int main() {     cin >> n;     for(int i = 1; i <= n; i++) {         for(int j = 1; j <= n; j++) {             cin >> g[i][j];         }     }     cout << dfs(1, 1) << endl;     return 0; }
   | 
 
注意到搜索过程中某些位置会被多次遍历,并且这些位置的结果是固定的,因此使用记忆化搜索进行如下优化,即记录下已遍历位置的答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   | #include <iostream> #include <cstring> using namespace std;
  const int N = 104;
  int g[N][N], n; int f[N][N];
 
  int dfs(int x, int y) {     if(~f[x][y]) return f[x][y];     if(x == n && y == n) return g[n][n];     if(x > n || y > n) return 1e9;     return f[x][y] =  min(f[x + 1][y] = dfs(x + 1, y), 					      f[x][y + 1] = dfs(x, y + 1)) + g[x][y]; }
  int main() {     memset(f, -1, sizeof f);     cin >> n;     for(int i = 1; i <= n; i++) {         for(int j = 1; j <= n; j++) {             cin >> g[i][j];         }     }     cout << dfs(1, 1) << endl;     return 0; }
   | 
 
最终,在记忆化搜索代码的基础上,可以写出最终的动态规划代码,对于求解最小值的问题,需要注意边界条件和动态规划数组的初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
   | #include <iostream> #include <cstring> using namespace std;
  const int N = 103;
  int n; int g[N][N], dp[N][N];
  int main() {     cin >> n;     for(int i = 1; i <= n; i++) {         for(int j = 1; j <= n; j++) {             cin >> g[i][j];         }        }     memset(dp, 0x3f, sizeof dp);     for(int i = 1; i <= n; i++) {         for(int j = 1; j <= n; j++) { 			             if(i == 1 && j == 1) dp[i][j] = 0;             else dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]);             dp[i][j] += g[i][j];         }     }     cout << dp[n][n] << endl;     return 0; }
   | 
 
明白以上原理后可以在AcWing算法提高课中进行进一步的练习。
其他示例代码
最大连续子序列之和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | #include<bits/stdc++.h> #define INF 10000009 using namespace std; int main() { 	int n; 	cin >> n; 	vector<int> nums(n); 	for (int i = 0; i < n; i++) { 		cin >> nums[i]; 	} 	int tmpSum = 0, maxSum = 0; 	for (auto i : nums) { 		tmpSum = max(tmpSum + i, i); 		maxSum = max(tmpSum, maxSum); 	} 	cout << maxSum << endl; 	return 0; }
   | 
 
矩阵连乘问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
   | #include<bits/stdc++.h> #define INF 1000000009 using namespace std; int main() { 	int n; 	cin >> n; 	vector<pair<int, int>> mSize(n + 1); 	mSize[0] = pair<int, int>(-1, -1); 	for (int i = 1; i <= n; i++){ 		cin >> mSize[i].first >> mSize[i].second; 	} 	vector<vector<int>> dp(n + 1, vector<int>(n + 1, INF)); 	for (int i = 1; i <= n; i++) { 		dp[i][i] = 0; 	} 	for (int len = 2; len <= n; len++) {  		for (int i = 1; i <= n - len + 1; i++) {  			int j = i + len - 1;                     			for (int k = i; k < j; k++) {   				dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + mSize[i].first * mSize[k].second * mSize[j].second); 			} 		} 	} 	cout << dp[1][n] << endl;     
 
 
 
  	return 0; }
   | 
 
集合划分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
   | #include <bits/stdc++.h> using namespace std; typedef long long int ll; int main() { 	int n; 	cin >> n; 	vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0)); 	 	for (int i = 1; i <= n; i++) { 		for (int j = 1; j <= n; j++) { 			if (i >= j) { 				if (j == 1) { 					dp[i][j] = 1; 				} 				else { 					dp[i][j] = dp[i - 1][j] * j + dp[i - 1][j - 1]; 				} 			} 		} 	} 	ll ans = 0; 	for (int i = 1; i <= n; i++) { 		ans += dp[n][i]; 	} 	cout << ans << endl; 	return 0; }
   | 
 
数字三角形模型
AcWing 1015
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
   | #include <bits/stdc++.h> using namespace std;
  const int N = 101; const int dx[2] = {0, 1}, dy[2] = {1, 0}; int g[N][N], f[N][N]; int n, m;
  void solve() {     cin >> n >> m;     memset(f, 0, sizeof f);     for(int i = 1; i <= n; i++) {         for(int j = 1; j <= m; j++) {             cin >> g[i][j];         }     }     for(int i = 1; i <= n; i++) {         for(int j = 1; j <= m; j++) {             f[i][j] = max(f[i - 1][j], f[i][j - 1]) + g[i][j];         }     }     cout << f[n][m] << endl; }
  int main() {     int t;     cin >> t;     while(t--) {         solve();     }     return 0; }
   | 
 
AcWing 1018
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
   | #include <bits/stdc++.h> using namespace std;
  const int N = 101;
  int g[N][N], dp[N][N]; int n;
  int main() {     cin >> n;     for(int i = 1; i <= n; i++) {         for(int j = 1; j <= n; j++) {             cin >> g[i][j];         }     }     memset(dp, 0x3f, sizeof dp);     for(int i = 0; i <= n; i++) dp[i][0] = dp[0][i] = 0;     for(int i = 1; i <= n; i++) {         for(int j = 1; j <= n; j++) {             if(i > 1 && j > 1) dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]);             else if(i == 1) dp[i][j] = dp[i][j - 1];             else dp[i][j] = dp[i - 1][j];             dp[i][j] +=  g[i][j];         }     }     cout << dp[n][n] << endl;     return 0; }
   | 
 
AcWing 1027
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
   | #include <bits/stdc++.h> using namespace std;
 
 
  const int N = 11;
  int g[N][N], f[2 * N][N][N]; int n;
  bool valid(int x, int y) {     return x >= 1 && y >= 1 && x <= n && y <= n; }
  int main() {     cin >> n;     int a, b, c;     while(cin >> a >> b >> c && (a + b + c)) {         g[a][b] = c;     }     for(int len = 2; len <= 2 * n; len++) {         for(int i1 = 1; i1 <= n; i1++) {             for(int i2 = 1; i2 <= n; i2++) {                 int j1 = len - i1, j2 = len - i2;                 if(valid(j1, j2)) {                     int t = g[i1][j1];                     if(i1 != i2) t += g[i2][j2];                     for(int i = 0; i < 4; i++) {                         f[len][i1][i2] = max(f[len][i1][i2], f[len - 1][i1 -(i & 1)][i2 - ((i >> 1) & 1)] + t);                     }                 }             }         }     }     cout << f[n * 2][n][n] << endl;     return 0; }
   | 
 
AcWing 275
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
   | #include <bits/stdc++.h> using namespace std;
 
 
  const int N = 55;
  int g[N][N], f[2 * N][N][N]; int n, m;
  bool valid(int y) {     return y >= 1 && y <= m; }
  int main() {     cin >> n >> m;     int a, b, c;     for(int i = 1; i <= n; i++){         for(int j = 1; j <= m; j++) {             cin >> g[i][j];         }     }     for(int len = 2; len <= m + n; len++) {         for(int i1 = 1; i1 <= n; i1++) {             for(int i2 = 1; i2 <= n; i2++) {                 int j1 = len - i1, j2 = len - i2;                 if(valid(j1) && valid(j2)) {                     int t = g[i1][j1];                     if(i1 != i2 || j1 != j2) t += g[i2][j2];                     for(int i = 0; i < 4; i++) {                         f[len][i1][i2] = max(f[len][i1][i2], f[len - 1][i1 -(i & 1)][i2 - ((i >> 1) & 1)] + t);                     }                 }             }         }     }     cout << f[n + m][n][n] << endl;     return 0; }
   | 
 
最长上升子序列模型
AcWing 1017
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
   | #include <iostream> using namespace std;
  const int N = 110;
  int arr[N], dp_inc[N], dp_dec[N]; int ans = -1, n;
  void solve() {     ans = -1;     cin >> n;     for(int i = 1; i <= n; i++) {         cin >> arr[i];         dp_inc[i] = dp_dec[i] = 1;     }     for(int i = 1; i <= n; i++) {         for(int j = 1; j < i; j++) {             if(arr[i] > arr[j]) dp_inc[i] = max(dp_inc[i], dp_inc[j] + 1);             if(arr[i] < arr[j]) dp_dec[i] = max(dp_dec[i], dp_dec[j] + 1);         }         ans = max(ans, max(dp_inc[i], dp_dec[i]));     }     cout << ans << endl; }
  int main() {     int t;     cin >> t;     while(t--) {         solve();     }     return 0; }
   | 
 
AcWing 1014
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
   | #include <iostream> using namespace std;
  const int N = 1010;
  int arr[N], dp_inc[N], dp_dec[N], n;
  int main() {     int ans = -1;     cin >> n;     for(int i = 1; i <= n; i++) {         cin >> arr[i];         dp_inc[i] = dp_dec[i] = 1;     }     for(int i = 1; i <= n; i++) {         for(int j = 1; j < i; j++) {             if(arr[i] > arr[j]) dp_inc[i] = max(dp_inc[i], dp_inc[j] + 1);         }     }     for(int i = n; i >= 1; i--) {         for(int j = n; j > i; j--) {             if(arr[i] > arr[j]) dp_dec[i] = max(dp_dec[i], dp_dec[j] + 1);         }     }     for(int i = 1; i <= n; i++) {         ans = max(ans, dp_dec[i] + dp_inc[i] - 1);     }     cout << ans << endl;     return 0; }
   | 
 
AcWing 482
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
   | #include <iostream> using namespace std;
  const int N = 1010;
  int arr[N], dp_inc[N], dp_dec[N], n;
  int main() {     int ans = -1;     cin >> n;     for(int i = 1; i <= n; i++) {         cin >> arr[i];         dp_inc[i] = dp_dec[i] = 1;     }     for(int i = 1; i <= n; i++) {         for(int j = 1; j < i; j++) {             if(arr[i] > arr[j]) dp_inc[i] = max(dp_inc[i], dp_inc[j] + 1);         }     }     for(int i = n; i >= 1; i--) {         for(int j = n; j > i; j--) {             if(arr[i] > arr[j]) dp_dec[i] = max(dp_dec[i], dp_dec[j] + 1);         }     }     for(int i = 1; i <= n; i++) {         ans = max(ans, dp_dec[i] + dp_inc[i] - 1);     }          cout << n - ans << endl;     return 0; }
   | 
 
背包模型
AcWing 423
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | #include <iostream>
  using namespace std;
  #define MAX_N 101 #define MAX_V 1010
  int costs[MAX_N], values[MAX_N], dp[MAX_V];
  int main() {     int v, n;     cin >> v >> n;     for(int i = 1; i <= n; i++) {         cin >> costs[i] >> values[i];     }     for(int i = 1; i <= n; i++) {         for(int j = v; j >= costs[i]; j--) {             dp[j] = max(dp[j], dp[j - costs[i]] + values[i]);         }     }     cout << dp[v] << endl;     return 0; }
   |