广泛用于求解组合优化问题
使用这种技术的算法,不是递归调用自身,但是问题的基础解通常是用递归函数的形式来说明的。
最长公共子序列问题
问题描述
有两个长度分别为n和m的字符串A和B,确定A和B中最长公共子序列的长度。这里,A=a1a2⋯anA = a_1a_2\cdots a_nA=a1a2⋯an的子序列是一个形式为ai1ai2...aika_{i_1}a_{i_2}...a_{i_k}ai1ai2...aik的字符串,其中每个iji_jij都在1和n之间,并且1≤i1<i1<⋯<ik≤n1\leq i_1<i_1<\cdots <i_k\leq n1≤i1<i1<⋯<ik≤n。假设A=zxyxyz,B=xyyzx,很很显最长公共子序列为xyyz。
解决思路
为了使用动态规划技术,首先寻找一个递推公式,令A=a1a2⋯an,B=b1b2⋯bmA = a_1a_2\cdots a_n, B = b_1b_2\cdots b_mA=a1a2⋯an,B=b1b2⋯bm,令L[i.,j]L[i.,j]L[i.,j]表示a1a2⋯aia_1a_2\cdots a_ia1a2⋯ai和b1b2⋯bjb_1b_2\cdots b_jb1b2⋯bj的最长公共子序列的长度。注意,iii和jjj可能是0,此时 L=0L = 0L=0。
下面给出递推式
L[i,j]={0if i=0 or j=0L[i−1,j−1]+1if i>0,j>0 and ai=bjmax{L[i,j−1],L[i−1,j]}if i>0,j>0 and ai≠bj
L[i, j] = \begin{cases}
0 & \text {if $i= 0$ or $ j=0$} \\
L[i-1,j-1]+1&\text{if $ i>0,j>0$ and $a_i = b_j$}\\
max\lbrace L[i,j-1],L[i-1,j]\rbrace & \text{if $i>0,j>0$ and $a_i\neq b_j$}
\end{cases}
L[i,j]=⎩⎪⎨⎪⎧0L[i−1,j−1]+1max{L[i,j−1],L[i−1,j]}if i=0 or j=0if i>0,j>0 and ai=bjif i>0,j>0 and ai̸=bj
算法
对于每一对i和j的值,0≤i≤n,0≤j≤m0\leq i\leq n,0\leq j\leq m0≤i≤n,0≤j≤m,用一个(n+1)∗(m+1)(n+1)*(m+1)(n+1)∗(m+1)大小的表来计算存储L[i,j]L[i,j]L[i,j]的值
\\算法LCS
\\输入:字符串A和B,长度分别为n和m
\\输出:A和B最长公共子序列的长度
main(){
for i = 0:n
L[i,0] = 0
for j = 0:m
L[0,j] = 0
for i = 1:n
for j = 1:m
if a[i] = b[j]
L[i,j] = L[i-1,j-1] + 1
else
L[i,j] = max{L[i,j-1],L[i-1,j]}
return L[n, m]
}
0/1背包问题
问题描述
设U={u1,u2,⋯ ,un}U=\lbrace u_1,u_2, \cdots ,u_n\rbraceU={u1,u2,⋯,un}是一个准备放入容量为CCC的背包中的nnn项物品的集合。对于1≤j≤n1\leq j \leq n1≤j≤n,令sjs_jsj和vjv_jvj分别为第jjj项物品的体积和价值,这里,C,sj,vjC,s_j,v_jC,sj,vj和jjj都是正整数。我们要解决的问题四用U中的一些物品来装满背包,这些物品的总体积不超过CCC,然而要使他们的总价值最大。
解决思路
V[i,j]V[i,j]V[i,j]表示从前iii项{u1,u2,⋯ ,ui}\lbrace u_1,u_2, \cdots ,u_i\rbrace{u1,u2,⋯,ui}中取出来的装入体积为jjj的背包的物品的最大价值。这里,0≤i≤n0\leq i\leq n0≤i≤n,0≤j≤C0\leq j \leq C0≤j≤C。找出递推式
V[i,j]={0if i=0 or j=0V[i−1,j]if j<simax{V[i−1,j],V[i−1,j−si]+vi}if i>0,j≥si
V[i, j] = \begin{cases}
0 & \text {if $i= 0$ or $ j=0$} \\
V[i-1,j]&\text{if $ j<s_i$}\\
max\lbrace V[i-1,j],V[i-1,j-s_i]+v_i\rbrace & \text{if $i>0,j\geq s_i$}
\end{cases}
V[i,j]=⎩⎪⎨⎪⎧0V[i−1,j]max{V[i−1,j],V[i−1,j−si]+vi}if i=0 or j=0if j<siif i>0,j≥si
算法
\\算法KNAPSACK
\\输入:物品集合U={u1, u2, ..., un},体积分别为s1, s2,...,sn,价值分别为v1, v2, v3,..., vn,容量为C的背包
\\输出:满足条件的最大总价值
main(){
for i = 0:n
V[i,0] = 0
for j = 0:C
V[0,j] = 0
for i = 1:n
for j = 1:C
if s[i] <= j
V[i,j] = max{v[i,j],L[i-1,j-si]+vi}
else
V[i,j] = V[i-1,j]
return V[n, C]
}
总结
很明显,无论是公共子序列还是背包问题,最关键的就是能找到合适的递推公式。说白了,还是要数学直觉强,加上见多识广。数学才是王道啊!tt,你说是不是。
卡片计分——附加题
问题描述
小a和小b玩一个游戏,有n张卡牌,每张上面有两个正整数x,y。取一张牌时,个人积分增加x,团队积分增加y。求小a,小b各取若干张牌,使得他们的个人积分相等,且团队积分最大。
输入描述
第一行n,接下来n行,每行两个正整数x,y
输出描述
一个整数,表示团队积分的最大值
示例
输入:
4
3 1
2 2
1 4
1 4
输出:
10
数据范围
0 < n < 100
0 < x < 1000
0 < y < 1e6
算法
很难想象这道题竟然也能用动态规划,数学果然令人畏惧!我当然也是看了网上的才懂。以L[i,j]L[i,j]L[i,j]表示前iii张牌中,个人积分相差jjj时,获得的最大团队积分,0≤i≤n,0≤j≤xmax0\leq i\leq n,0\leq j\leq x_{max}0≤i≤n,0≤j≤xmax。xxx数组表示个人积分,yyy数组表示团队积分。这里给出递推式,注意:个人认为网上的程序(就是t1,t2t1,t2t1,t2条件)有一点问题,因为要考虑$L[i-1][j-x[i-1]] 是否大于0,即如果等于0,那么就无法组成前是否大于0,即如果等于0,那么就无法组成前是否大于0,即如果等于0,那么就无法组成前i张牌中,个人积分相差张牌中,个人积分相差张牌中,个人积分相差j-x[i-1]时的情况,此时时的情况,此时时的情况,此时t1只能等于0;只能等于0;只能等于0;j < x[i-1]也要考虑;还有就是积分相差为0的情况,因为此情况必然存在,即两边都不拿牌,也就是也要考虑;还有就是积分相差为0的情况,因为此情况必然存在,即两边都不拿牌,也就是也要考虑;还有就是积分相差为0的情况,因为此情况必然存在,即两边都不拿牌,也就是j == x[i-1]的特殊情况。
L[i,j]={0if i=0max{L[i−1,j],t1,t2}if i>0
L[i, j] = \begin{cases}
0 & \text {if $i= 0$} \\
max\lbrace L[i-1,j],t1,t2\rbrace & \text{if $i>0$}
\end{cases}
L[i,j]={0max{L[i−1,j],t1,t2}if i=0if i>0
t1={0othersL[i−1][diff]+y[i−1] if L[i−1][diff]>0 or diff==0)diff=abs(j−x[i−1])
t1 = \begin{cases}
0 & \text {others} \\
L[i-1][diff] + y[i-1] &\text{ if $L[i-1][diff] > 0$ or $diff== 0)$}
\end{cases}
diff = abs(j - x[i-1])
t1={0L[i−1][diff]+y[i−1]others if L[i−1][diff]>0 or diff==0)diff=abs(j−x[i−1])
t2={0othersL[i−1][j+x[i−1]]+y[i−1]if j+x[i−1]≤xmax and L[i−1][j+x[i−1]]>0
t2 = \begin{cases}
0 & \text {others} \\
L[i-1][j+x[i-1]] + y[i-1] &\text{if $j+x[i-1] \leq x_{max}$ and $L[i-1][j+x[i-1]] > 0$}
\end{cases}
t2={0L[i−1][j+x[i−1]]+y[i−1]othersif j+x[i−1]≤xmax and L[i−1][j+x[i−1]]>0
程序
//c++ 源码
#include<iostream>
#include<vector>
using namespace std;
/*
4
3 1
2 2
1 4
1 4
如果使用网上的一些程序,第二个样例会失败
4
3 1
4 2
5 3
10 4
*/
int max3(int x, int y, int z){
if(x < y)
x = y;
if(x < z)
x = z;
return x;
}
int main(){
int n;
cin >> n;
int *x = new int[n];
int *y = new int[n];
int max = 0;
vector<vector<int> > help;
for(int i = 0; i < n; ++ i){
cin >> x[i] >> y[i];
if(x[i] > max)
max = x[i];
}
for(int i = 0; i < n + 1; ++ i){
vector<int> temp(max + 1, 0);
help.push_back(temp);
}
for(int i = 1; i < n + 1; ++ i){
printf("%d ",x[i-1]);
for(int j = 0; j < max + 1; ++ j){
int temp1 = 0, temp2 = 0;
int diff = j - x[i-1];
if (diff < 0)
diff = 0 - diff;
if((help[i-1][diff] > 0 || diff == 0)
//注意,就是条件这里不太一样
temp1 = help[i-1][diff] + y[i-1];
if( j + x[i-1] <= max && help[i-1][j+x[i-1]] > 0)
temp2 = help[i-1][j+x[i-1]] + y[i-1];
help[i][j] = max3(help[i - 1][j], temp1, temp2);
printf("%d ",help[i][j]);
}
printf("\n");
}
cout << help[n][0] << endl;
delete []x;
delete []y;
}