1(最長公共子串(注意和最長公共子序列區別))
兩個字符串str1和str2,長度分別為(l1,l2)
dp[i][j]表示以兩個字符串分別以第i和第j個字符結尾所能達到的公共子序列的長度,由於下面涉及到i-1和j-1,那么這個時候我們一般從i=1和j=1開始到i<=len1, j<=len2。
if(str[i-1]=str[j-1])
dp[i][j]=dp[i-1][j-1]+1;
if(str[i-1]!=str[j-1])
dp[i][j]=0;
0 ; i = 0或j= 0;
就有 dp = dp[i][j] = dp[i-1][j-1] + 1; i > 0且j> 0 且ch1[i-1]= ch2[j-1];
dp[i][j]= 0; i > 0且j> 0 且ch1[i-1]!= ch2[j-1];
注意dp[i][0]=0(0<=i<=max(l1,l2);dp[0][i]=0(0<=i<=max(l1,l2);
最長公共字串要求在原來字符中滿足是連續的,最長公共子序列則不要求
最長公共子序列:
根據最長公共子序列問題的性質,我們可以規定dp[i][j]為字符串1的前i個字符和字符串2的前j個字符的最長公共子序列的長度, 由於下面涉及到i-1和j-1,那么這個時候我們一般從i=1和j=1開始到i<=len1, j<=len2。
1ch1[i-1] = ch2[j-1] ,那么dp[i][j]= dp[i-1][j-1] + 1;
2 ch1[i-1] != ch2[j-1] ,那么我們知道ch1[i]和ch2[j]不可能在同一個公共子序列里出現,那么這個時候的最長的子序列可能以ch1[i]或ch2[j]結尾,那么由於dp[i][j]= max {dp[i-1][j] , dp[i][j-1]};
這個時候所有i=0或j=0的dp[i][j]= 0;
0 ; i = 0或j= 0;
就有 dp = dp[i][j] = dp[i-1][j-1] + 1; i > 0且j> 0 且ch1[i-1]= ch2[j-1];
dp[i][j]= max {dp[i-1][j] , dp[i][j-1]};i > 0且j> 0且ch1[i-1]!= ch2[j-1];
2(最長上升或下降子序列)
給定一個序列a1,a2..........an;
dp[i]表示以ai結尾的最長上升子序列長度(下降相反)
核心代碼:
for(i=1;i<=n;i++){
dp[i]=1;
for(k=1;k<i;k++){
if(ak<ai&&dp[i]<dp[k]+1)
dp[i]=dp[k]+1;
}
}
注意最長不上升或不下降子序列問題
3(最大子序列的和問題)
給定一個序列a1,a2..........an;
求子序列的和最大問題dp[i]表示以ai結尾的子序列和,max為最大子序列和
核心:
1如果輸入的數據全部為負數則最大值就是序列中的一個最大值
2如果有正數
for(i=1;i<=n;i++){
dp[i]=dp[i-1]+ai;
if(dp[i]<0)
dp[i]=0;
if(max<dp[i])
max=dp[i];
}
4(數塔問題)
給定一個數組s[n][m]構成一個數塔求從最上面走到最低端經過的路徑和最大
我么采用至底向上的思路求解問題(注意從倒數第二行開始)
dp[i][j]表示走到第i行第j個的最大值
那么就有dp[i][j]=max{dp[i-1][j-1],dp[i-1][j]}+s[i][j];
for(i=n-1;i>=1;i--){
for(j=1;j<=i;j++){
dp[i][j]=max{dp[i-1][j-1],dp[i-1][j]}+s[i][j]
}
}
最后dp[1][1]即為最大值
5(01背包問題)
有N件物品和一個容量為V的背包。第i件物品的體積是v[i],價值是c[i]。求解將哪些物品裝入背包可使價值總和最大。
我們知道對於沒一件物品我們有兩種可能就是放與不放
dp[i][j]表示第i件物品放入容量為j的背包所得的最大價值
dp[i][j]=max{dp[i-1][j-v[i]]+c[i],dp[i-1][j]};
這里我們從j=V倒推回來的話可以優化成
dp[j]=max{dp[j],dp[j-v[i]]+c[i]};
核心代碼:
for(i=1;i<=n;i++){
for(j=V;j>=0;j--){
if(j>=v[i])
dp[j]=max{dp[j],dp[j-v[i]]+c[i]};
}
}
dp[v]即為最大的價值
6(完全背包問題)
有N種物品和一個容量為V的背包,每種物品都有無限件可用。第i種物品的體積是v[i],價值是c[i]。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量,且價值總和最大。
這時候對於沒見物品就不是放與不放的問題了,而是放0件1件.......
這時候我們可以像01背包一樣
dp[i][j]表示容量為j的背包第i件物品是否要再一次放入所以我們要從0-V順序循環
dp[i][j]=max{dp[i-1][j-v[i]]+c[i],dp[i-1][j]}(注意這里和01背包一樣但是求解的過程不同)
優化后:dp[j]=max{dp[j],dp[j-v[i]]+c[i]};
核心代碼:
for(i=1;i<=n;i++){
for(j=v[i];j<=v;j++){//注意這里是從v[i]開始到V
if(j>=v[i])
dp[j]=max{dp[j],dp[j-v[i]]+c[i]};
}
}
注意這列求出的dp[v]是最大的因為一直疊加
7(多重背包問題)
有N種物品和一個容量為V的背包。第i種物品最多有n[i]件可用,每件費用是c[i],價值是w[i]。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量,且價值總和最大。
因為對於第i種物品有n[i]+1種策略:取0件,取1件……取n[i]件。
重點:令dp[i][j]表示前i種物品恰放入一個容量為j的背包的最大價值
狀態轉移方程:dp[i][j]=max{dp[i-1][v-k*v[i]]+k*c[i]|0<=k<=n[i]};(k表示第i種物品放入k件);
核心代碼:
for(i=1;i<=n;i++){
for(j=v;j>=0;j--){
for(k=1;k<=n[i];k++){
if(j>=k*v[i])
dp[i][j]=max(dp[i-1][v-k*v[i]]+k*c[i])
}
}
8: (二維費用的背包問題)
二維費用的背包問題是指:對於每件物品,具有兩種不同的費用;選擇這件物品必須同時付出這兩種代價;對於每種代價都有一個可付出的最大值(背包容量)。問怎樣選擇物品可以得到最大的價值。設這兩種代價分別為代價1和代價2,第i件物品所需的兩種代價分別為a[i]和b[i]。兩種代價可付出的最大值(兩種背包容量)分別為V和U。物品的價值為w[i];
費用加了一維,只需狀態也加一維即可。設f[i][v][u]表示前i件物品付出兩種代價分別為v和u時可獲得的最大價值。
狀態轉移方程就是:
f[i][v][u]=max{f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+w[i]}
9(最大子段和問題(最大子序列的和不同))
給定一個序列為a1,a2,a3......an;
要求:求出這個序列里面找到一個子段和最大
dp[i]表示以第i個元素結束,求出所有的“以第i個元素結束的連續數組最大和dp[i]
就有:
1如果dp[i-1]>0,無論ai為何值,有dp[i]=dp[i-1]+ai;
2如果dp[i-1]<=0;舍棄,重新令dp[i]=ai;(因為dp[i-1]為負數無論ai為什么值加上去都會減少)
狀態轉移方程:dp[i]=dp[i-1]+ai (dp[i-1]>0)
dp[i]=ai(dp[i-1]<=0)
12(最大m子段和)
在限制條件增加一維時,可以將狀態也相應的增加一維,來進行狀態轉移
以dp[i][j]表示以第i個元素為結尾,使用j個子段所能達到的最大值(這一維的狀態,正是對應了新的限制條件!)這樣就很容易寫出
狀態轉移方程:
dp[i][j]= max{ dp[i - 1][j] + a[i], dp[i - k][j - 1] + a[i]}( j - 1 <= k <n - m + j).
1 dp[i - 1][j] + a[i] (把第i個元素包含在最后一個子段內)
2 dp[i - k][j - 1] + a[i], j - 1 <= k < n - m + j(第i個元素單獨為一子串)
13矩陣連乘
問題描述: 給定一序列的矩陣要求找到一種矩陣連乘的順序,使得連乘的次數最少
思路: 建立遞推表達式,利用動態規划的方式(m[i][j]表示第i個矩陣至第j個矩陣這段的最優解,還有對於兩個矩陣M(i,j)*S(j,k)則需要i*j*k次乘法)
1顯然如果i=j,則m[i][j]這段中就一個矩陣,需要計算的次數為0;
2如果i < j,則m[i][j]=min{m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]},其中k,在i與j之間游盪,所以i<=k<j;
3因為你要保證在計算m[i][j]查找m[i][k]和m[k+1][j]的時候,m[i][k]和m[k+1][j]已經計算出來了
所以有動態轉移方程:
m[i][j]={ 0 , i=j};
m[i][j]={min{m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]} , i!=j};
m[1][n]即為最終求解
白書上面寫道:記憶華搜索固然沒有問題,但如果要寫成遞推,無論按照i還是j的遞增或遞減均不爭確。正確的方法是按照j-i遞增的順序遞推,因為長區間的值依賴於短區間的值
模板:
int dp[MAXN][MAXN];//存儲最小的就算次數
int s[MAXN][MAXN];//存儲斷點,用在輸出上面
核心代碼:
int i , j ,tmp;
for(l= 2 ; l <= n ; l++){//j-i的長度,由於長度為1是相同的矩陣那么為0不用計算
for(i = 1 ; i <= n-l+1 ; i++){//由於j-i =l - 1 , 那么j的最大值為n,所以i上限為 n - l+1;
j = i+l-1;//由於j-i = l - 1 , 那么j = l+i-1
dp[i][j] = dp[i+1][j] + r[i]*c[i]*c[j];//初始化,就是k = i;
s[i][j] = i;
for(k = i+1 ; k < j ; k++){//循環枚舉k i < k < j
tmp = dp[i][k] + dp[k+1][j] + r[i]*c[k]*c[j];
if(dp[i][j] > tmp){
dp[i][j] = tmp;//更新為最小值
s[i][j] = k;
}
}
}
}
輸出:
//遞歸調用輸出
void output(int i , int j){
if(i == j){
printf("A%d" , i);//當兩個相等的時候就不用繼續遞歸就輸出A
return;//返回上一層
}
else{
printf("(");
output(i , s[i][j]);
printf(" x ");
output(s[i][j]+1 , j);
printf(")");
}
}
14區間DP
區間動態規划問題一般都是考慮,對於每段區間,他們的最優值都是由幾段更小區間的最優值得到,是分治思想的一種應用,將一個區間問題不斷划分為更小的區間直至一個元素組成的區間,枚舉他們的組合,求合並后的最優值。
1 設F[i,j](1<=i<=j<=n)表示區間[i,j]內的數字相加的最小代價
2 最小區間F[i,i]=0(一個數字無法合並,∴代價為0)
3 每次用變量k(i<=k<=j-1)將區間分為[i,k]和[k+1,j]兩段
4《區間DP模板,代碼》
for(int p = 1 ; p <= n ; p++){//p是區間的長度,作為階段
for(int i = 1 ; i <= n-p ; i++){//i是窮舉區間的起點
int j = i+p;//j為區間的終點
for(int k = i+1 ; k < j ; k++)//狀態轉移
dp[i][j] = min{dp[i][k]+dp[k+1][j]+w[i][j]};//這個是看具體的狀態轉移方程
或 dp[i][j] = max{dp[i][k]+dp[k+1][j]+w[i][j]};//求最大
或 dp[i][j] = min{dp[i][k]+dp[k][j]+w[i][j]}//有的是要從k開始不是k+1
}
}