广泛用于求解组合优化问题
使用这种技术的算法,不是递归调用自身,但是问题的基础解通常是用递归函数的形式来说明的。
最长公共子序列问题
问题描述
有两个长度分别为n和m的字符串A和B,确定A和B中最长公共子序列的长度。这里, A = a 1 a 2 ⋯ a n A = a_1a_2\cdots a_n A=a1a2⋯an的子序列是一个形式为 a i 1 a i 2 . . . a i k a_{i_1}a_{i_2}...a_{i_k} ai1ai2...aik的字符串,其中每个 i j i_j ij都在1和n之间,并且 1 ≤ i 1 < i 1 < ⋯ < i k ≤ n 1\leq i_1<i_1<\cdots <i_k\leq n 1≤i1<i1<⋯<ik≤n。假设A=zxyxyz,B=xyyzx,很很显最长公共子序列为xyyz。
解决思路
为了使用动态规划技术,首先寻找一个递推公式,令
A
=
a
1
a
2
⋯
a
n
,
B
=
b
1
b
2
⋯
b
m
A = a_1a_2\cdots a_n, B = b_1b_2\cdots b_m
A=a1a2⋯an,B=b1b2⋯bm,令
L
[
i
.
,
j
]
L[i.,j]
L[i.,j]表示
a
1
a
2
⋯
a
i
a_1a_2\cdots a_i
a1a2⋯ai和
b
1
b
2
⋯
b
j
b_1b_2\cdots b_j
b1b2⋯bj的最长公共子序列的长度。注意,
i
i
i和
j
j
j可能是0,此时
L
=
0
L = 0
L=0。
下面给出递推式
L
[
i
,
j
]
=
{
0
if
i
=
0
or
j
=
0
L
[
i
−
1
,
j
−
1
]
+
1
if
i
>
0
,
j
>
0
and
a
i
=
b
j
m
a
x
{
L
[
i
,
j
−
1
]
,
L
[
i
−
1
,
j
]
}
if
i
>
0
,
j
>
0
and
a
i
≠
b
j
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 ≤ m 0\leq i\leq n,0\leq j\leq m 0≤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 = { u 1 , u 2 , ⋯   , u n } U=\lbrace u_1,u_2, \cdots ,u_n\rbrace U={u1,u2,⋯,un}是一个准备放入容量为 C C C的背包中的 n n n项物品的集合。对于 1 ≤ j ≤ n 1\leq j \leq n 1≤j≤n,令 s j s_j sj和 v j v_j vj分别为第 j j j项物品的体积和价值,这里, C , s j , v j C,s_j,v_j C,sj,vj和 j j j都是正整数。我们要解决的问题四用U中的一些物品来装满背包,这些物品的总体积不超过 C C C,然而要使他们的总价值最大。
解决思路
V
[
i
,
j
]
V[i,j]
V[i,j]表示从前
i
i
i项
{
u
1
,
u
2
,
⋯
 
,
u
i
}
\lbrace u_1,u_2, \cdots ,u_i\rbrace
{u1,u2,⋯,ui}中取出来的装入体积为
j
j
j的背包的物品的最大价值。这里,
0
≤
i
≤
n
0\leq i\leq n
0≤i≤n,
0
≤
j
≤
C
0\leq j \leq C
0≤j≤C。找出递推式
V
[
i
,
j
]
=
{
0
if
i
=
0
or
j
=
0
V
[
i
−
1
,
j
]
if
j
<
s
i
m
a
x
{
V
[
i
−
1
,
j
]
,
V
[
i
−
1
,
j
−
s
i
]
+
v
i
}
if
i
>
0
,
j
≥
s
i
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]表示前
i
i
i张牌中,个人积分相差
j
j
j时,获得的最大团队积分,
0
≤
i
≤
n
,
0
≤
j
≤
x
m
a
x
0\leq i\leq n,0\leq j\leq x_{max}
0≤i≤n,0≤j≤xmax。
x
x
x数组表示个人积分,
y
y
y数组表示团队积分。这里给出递推式,注意:个人认为网上的程序(就是
t
1
,
t
2
t1,t2
t1,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
]
=
{
0
if
i
=
0
m
a
x
{
L
[
i
−
1
,
j
]
,
t
1
,
t
2
}
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
t
1
=
{
0
others
L
[
i
−
1
]
[
d
i
f
f
]
+
y
[
i
−
1
]
if
L
[
i
−
1
]
[
d
i
f
f
]
>
0
or
d
i
f
f
=
=
0
)
d
i
f
f
=
a
b
s
(
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])
t
2
=
{
0
others
L
[
i
−
1
]
[
j
+
x
[
i
−
1
]
]
+
y
[
i
−
1
]
if
j
+
x
[
i
−
1
]
≤
x
m
a
x
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;
}