動態規劃演算法初步總結
阿新 • • 發佈:2022-03-14
動態規劃(DP)=狀態表示+狀態計算
其中:
狀態表示為:集合(前i-1個元素的有限集合構成的最優解)+屬性(max or min)
狀態計算為是否將第i個元素加入集合中,即通過判斷加入或不加入是否更符合優化目標決定
步驟可總結為:
- 輸入
- 為使迴圈順利進行,給目標陣列賦邊界值。多為一維陣列為第0位取零,二維陣列第0行第0列取零,對角線取零等
- 目標陣列賦初值, 可賦當前狀態-1時的值
- 目標陣列從1到n迴圈計算,判斷是否將第i個元素加入集合中
- 輸出陣列最後一項
關鍵在於構造遞推方程,正確理解狀態概念可以降低構造遞推方程的難度
目前DP問題程式碼大都沒有成型的步驟和體系,對初學者並不友好,初學者可能陷入僅僅背過一道題的程式碼而不會處理其它問題的境地。按照以上步驟處理可以解決很多簡單的DP問題,實現初步掌握這一類題。
例如:
①0-1揹包問題
#include <iostream> using namespace std; const int N = 1e3+10; int main() { int n, v; cin >> n >> v; int value[N], volume[N]; int key[N][N]; for (int i = 1; i <= n; i++) scanf("%d%d", &volume[i], &value[i]); //輸入部分 for (int i = 0; i <= n; i++) { key[i][0] = 0; key[0][i] = 0;//賦兩位下標取0時的邊界值 } for (int i = 1; i <= n; i++) { for (int j = 1; j <= v; j++) { key[i][j] = key[i - 1][j];//根據當前狀態減一給目標陣列賦初值 if (j - volume[i] >= 0) { int temp = key[i - 1][j - volume[i]] + value[i];if (temp > key[i][j]) key[i][j] = temp; }//判斷是否將第i個元素加入集合中 } } cout << key[n][v] << endl; return 0; }
②影象壓縮問題
#include <iostream> #include <cmath> using namespace std; const int N = 1e3+10; int main() { int n; cin >> n; int p[N], b[N]; for (int i = 1; i <= n; i++) scanf("%d", &p[i]); for (int i = 1; i <= n; i++) b[i] = ceil(log(p[i] + 1) / log(2)); int key[N]; key[0] = 0;//邊界值 for (int i = 1; i <= n; i++) key[i] = key[i - 1]+ b[i]+11; //根據當前狀態減一給目標陣列賦初值 for (int i = 1; i <= n; i++) { int bmax=0; for (int j =i-2; j >=1; j--) { if (b[j] > bmax) bmax = b[j]; int temp= key[j] + (i - j)*bmax+11; if (key[i] > temp) key[i] = temp; }//判斷是否將第i個元素加入集合中 } cout << key[n]; return 0; }
③公共子序列
#include <iostream> using namespace std; const int N = 1e3+10; int main() { int n,m; cin >> n>>m; char a[N], b[N]; cin >> a + 1 >> b +1; int key[N][N]; for (int i =0; i <= n; i++) key[i][0] = 0; for (int i = 0; i <=m; i++) key[0][i] = 0;//邊界取零 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { key[i][j] = max(key[i - 1][j],key[i][j-1]);//賦初值 if (a[i] == b[j]) key[i][j] =max(key[i-1][j-1]+1,key[i][j]); } } cout << key[n][m]; return 0; }
④矩陣連乘
#include <iostream>using namespace std; const int N = 1e2 + 10; int main() { int key[N][N],p[N]; int n; cin >> n; for (int i = 0; i <= n; i++) scanf("%d", &p[i]); for (int i = 0; i <= n; i++) key[i][i] = 0; //邊界值 for (int i = 1; i <= n; i++) { for (int j = i + 1; j <= n; j++) { key[i][j] = key[i][j-1]+p[i-1]*p[j-1]*p[j]; //賦間隔為一的初值 for (int k = i + 1; k <= j; k++) { int temp = key[i][k]+p[i-1]*p[k]*p[j]; if (temp < key[i][j]) key[i][j] = temp; } } } cout << key[1][n]; return 0; }
由以上例子可見,對於簡單動態規劃問題,牢記以上步驟有助於初學者初步入門。進一步提升還需要多多刷題思考總結。