【主要內容】
動態規划
背包類型的dp:01背包
線性dp:最長公共子序列,編輯距離
經典例題: 獨立任務最優調度,最大子段和
01背包
【題目鏈接】
https://www.acwing.com/problem/content/2/
【題目描述】
有 N 件物品和一個容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的體積是 vi,價值是 wi。
求解將哪些物品裝入背包,可使這些物品的總體積不超過背包容量,且總價值最大。 輸出最大價值。
【數據范圍】
0<N,V≤1000 0<vi,wi≤1000
【輸入樣例】
4 5
1 2
2 4
3 4
4 5
【輸出樣例】
8
【題解】 設 f[i][j]挑選前i個物品放入背包在容量為j時,獲取的最大價值。
在挑選第i個物品時,當能放入的情況下,寫出對應的狀態轉移方程:

即放入時,騰出對應的空間出來放物品,同時獲取對應的價值。
最后相比較,在放與不放之間取最大值
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 const int N = 1e3 + 10 ; 5 int f[N][N] , v[N] , w[N] ; 6 int main() 7 { 8 int n , V ; 9 scanf("%d%d",&n,&V); 10 for( int i = 1 ; i <= n ; i++ ){ 11 scanf("%d%d",&v[i],&w[i]); 12 } 13 14 //枚舉物品 15 for( int i = 1 ; i <= n ; i++ ){ 16 //枚舉背包容量 17 for( int j = 0 ; j <= V ; j ++ ){ 18 //如果能承載該物品,基於前i-1個物品的情況后放入,若能比不放 的價值大則替換 19 if( j >= v[i] ){ 20 f[i][j] = max( f[i-1][j] , f[i-1][j-v[i]] + w[i] ); 21 } 22 //若不能放入,則維持原來的價值. 23 else{ 24 f[i][j] = f[i-1][j] ; 25 } 26 } 27 } 28 printf("%d\n",f[n][V]); 29 return 0; 30 }
最長公共子序列
【題目鏈接】
https://www.acwing.com/problem/content/899/
【題目描述】
給定兩個長度分別為N和M的字符串A和B,求既是A的子序列又是B的子序列的字符串長度最長是多少。
【數據范圍】
1≤N≤1000
【輸入樣例】
4 5
acbd
abedc
【輸出樣例】
3
【題解】
對於字符串ABCDE BD是其中一個子序列
設f[ i ][ j ] 為A串前i個字符與B串前j個字符 兩者構成最長的子序列 的 長度。
狀態轉移方程為:
當兩個字符相同時

否則

其含義為:
在匹配第i個和第j個相符時,我們可以把問題轉移到
否則,問題拋給 f( i - 1 , j ) , f( i , j - 1 )
【具體代碼】
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 const int N = 1e3+10 ; 5 char A[N] , B[N] ; 6 int f[N][N] ; 7 int main() 8 { 9 int n , m ; 10 scanf("%d%d%s%s",&n,&m,A+1,B+1); 11 for( int i = 1 ; i <= n ; i++ ){ 12 for( int j = 1 ; j <= m ; j ++ ){ 13 if( A[i] == B[j] ){ 14 f[i][j] = f[i-1][j-1] + 1 ; 15 }else{ 16 f[i][j] = max( f[i-1][j] , f[i][j-1] ); 17 } 18 } 19 } 20 printf("%d\n",f[n][m]); 21 return 0 ; 22 }
編輯距離
【題目鏈接】
https://www.acwing.com/problem/content/904/
【題目描述】
給定兩個字符串A和B,現在要將A經過若干操作變為B,可進行的操作有:
-
刪除–將字符串A中的某個字符刪除。
-
插入–在字符串A的某個位置插入某個字符。
-
替換–將字符串A中的某個字符替換為另一個字符。
現在請你求出,將A變為B至少需要進行多少次操作。
【數據范圍】
1≤n,m≤1000
【輸入樣例】
10
AGTCTGACGC
11
AGTAAGTAGGC
【輸出樣例】
4
【題解】
設 f[i][j] 為A串前i個字符通過編輯變成B串中前j個字符 所需要最少的操作步數。
編輯有三種操作,對應着三種情況。
A串前i個字符編輯成B串前j個字符。
增加一個字符:

刪除一個字符:

修改一個字符:

f[ i ] [ j ] 就是以上三種情況取最小值即可。
【具體代碼】
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N = 1e3 + 10 ; 6 char A[N],B[N] ; 7 int f[N][N] ; 8 int main(){ 9 int n , m ; 10 scanf("%d%s%d%s",&n,A+1,&m,B+1); 11 12 memset( f , 0x3f , sizeof f ); 13 for( int i = 0 ; i <= n ; i++ ) f[i][0] = i ; 14 for( int j = 0 ; j <= m ; j++ ) f[0][j] = j ; 15 16 for( int i = 1 ; i <= n ; i++ ){ 17 for( int j = 1 ; j <= m ; j++ ){ 18 f[i][j] = min( min( f[i-1][j] + 1 , f[i][j-1] + 1 ) , f[i-1][j-1] + ( A[i] != B[j] ) ); 19 } 20 } 21 printf("%d\n",f[n][m]); 22 return 0 ; 23 }
最大子段和
【題目鏈接】
https://leetcode-cn.com/problems/maximum-subarray
【題目描述】
給定一個整數數組 nums ,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。
示例:
輸入: [-2,1,-3,4,-1,2,1,-5,4], 輸出: 6 解釋: 連續子數組 [4,-1,2,1] 的和最大,為 6。
【題解】
最大字段和,其字段是“連續”的一段,不是片段。
因為給定的一系列數里面可能有負數。
[-1,2,3,-6,7]序列中最大子段和值為7。
從左往右看,我們肯定是從一個正數開始的,[2,3]=5,但是遇到-6后發現不能繼續延伸了,因為此時已經累計和為-1,如果是繼承前面的累加和,而是直接從自己開始,所以是7.
根據上述分析后,我們直接定義
f[i] 為前i個數字最大子段和。
答案就是過程中找到。
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 const int N = 1e4 + 10 ; 5 int n ; 6 int a[N] ; 7 int f[N] ; 8 int res = -0x3f3f3f3f ; 9 int main() 10 { 11 scanf("%d",&n); 12 for( int i = 1 ; i <= n ; i++ ){ 13 scanf("%d",&a[i]); 14 } 15 for( int i = 1 ; i <= n ; i++ ){ 16 //從左邊累加過來或者從當前位置開始 17 f[i] = max( f[i-1] , 0 ) + a[i] ; 18 res = max( res , f[i] ) ; 19 } 20 /* 21 for( int i = 1 ; i <= n ; i++ ){ 22 printf("%3d",f[i]); 23 } 24 puts(""); 25 */ 26 printf("%d\n",res); 27 return 0; 28 } 29 /* 30 9 31 -2 1 -3 4 -1 2 1 -5 4 32 33 6 34 */
獨立任務最優調度
【題目描述】
獨立任務最優調度,又稱雙機調度問題:用兩台處理機A和B處理n個作業。設第i個作業交給機器A處理時所需要的時間是a[i],若由機器B來處理,則所需要的時間是b[i]。現在要求每個作業只能由一台機器處理,每台機器都不能同時處理兩個作業。設計一個動態規划算法,使得這兩台機器處理完這n個作業的時間最短(從任何一台機器開工到最后一台機器停工的總的時間)。研究一個實例:n=6, a = {2, 5, 7, 10, 5, 2}, b = {3, 8, 4, 11, 3, 4}.
【參考博客】
https://www.jianshu.com/p/1e8a1a617c3b
【題解】
其實應該有一個嚴格的壓維過程的,參考博客中已經展示出來了。
我認為可以直接看參考博客,簡單易懂。
【個人見解】
完全是個人見解。
問題其實就好比01背包問題。
兩台機器的最優調度,第一台機器處理的任務 好比背包里的物品,同時不在背包里的物品則為另外一台機器所處理的任務。
物品的代價是時間,價值也是時間。
背包問題最優解不再是背包里的價值最大,而是放進背包里的總價值,和沒有放入背包的物品的總價值 之間取的最大值。
首先要知道這個物品在背包時的價值與不放入背包時的價值不一樣。
放入時價值為:a[i] , 沒有放入時為:b[i]
如果有一種情況是:放入背包里所有的價值之和A,與其不放入的價值之和B。
答案就是max{A,B}。
但是這僅僅是對於一種情況來說的,
而所有情況中取最小值。
設f[ i ][ j ] 為完成前i個任務在背包為j時,不在背包里物品的總價值。
即j為放入背包中的總價值,而f[i][j]為不放入背包中的總價值。
答案為:Max{ j , f[ i ][ j ] }
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 const int N = 1e3 + 10 ; 5 int f[N][N] ; 6 int a[N] , b[N] ; 7 int n ; 8 int main() 9 { 10 scanf("%d",&n); 11 for( int i = 1 ; i <= n ; i++ ) scanf("%d",&a[i]); 12 for( int i = 1 ; i <= n ; i++ ) scanf("%d",&b[i]); 13 /* 14 memset( f , 0x3f , sizeof f ); 15 for( int i = 0 ; i < a[1] ; i++ ) f[0][i] = 0 ; 16 */ 17 int total_time_A = 0 ; 18 int res = 0x3f3f3f3f ; 19 for( int i = 1 ; i <= n ; i++ ){ 20 total_time_A += a[i] ; 21 for( int j = 0 ; j <= total_time_A ; j++ ){ 22 if( j < a[i] ){ 23 f[i][j] = f[i-1][j] + b[i] ; 24 }else{ 25 f[i][j] = min( f[i-1][j] + b[i] , f[i-1][j-a[i]] ); 26 } 27 if( i == n ) 28 res = min( res , max( f[n][j] , j ) ) ; 29 } 30 } 31 printf("%d\n",res); 32 return 0 ; 33 } 34 35 /* 36 6 37 2 5 7 10 5 2 38 3 8 4 11 3 4 39 40 15 41 */
