动态规划
一 般解法:
- 利用最优子结构定义一个关于解的目标值的递归方程,采用“自底向上”而非使用递归时的“自顶向下”。
- 将每个子问题的解保留,需要时可以查找。
走格子问题
问题: 从左下角走到右上角,每步只能往上、右方向,每个格子有值,求到终点值最大。
思路:
假设矩阵为如下形式:
0
1
.
.
N
N
+
1
0
0
0
.
.
0
0
1
0
A
[
1
,
1
]
.
.
A
[
1
,
N
]
0
.
.
0
A
[
i
,
1
]
A
[
i
,
j
]
A
[
i
,
N
]
0
N
0
A
[
N
,
1
]
.
.
A
[
N
,
N
]
0
N
+
1
0
0
.
.
0
0
\begin{array}{c|lll} {}&{0}&{1}&{..}&{N}&{N+1}\\ \hline {0}&{0}&{0}&{..}&{0}&{0}\\ {1}&{0}&{A[1,1]}&{..}&{A[1,N]}&{0}\\ {..}&{0}&{A[i,1]}&{A[i,j]}&{A[i,N]}&{0}\\ {N}&{0}&{A[N,1]}&{..}&{A[N,N]}&{0}\\ {N+1}&{0}&{0}&{..}&{0}&{0}\\ \end{array}
01..NN+100000010A[1,1]A[i,1]A[N,1]0......A[i,j]....N0A[1,N]A[i,N]A[N,N]0N+100000
则递推公式为:
f
[
i
,
j
]
=
{
0
i
=
N
+
1
或
j
=
0
max
(
f
[
i
+
1
,
j
]
,
f
[
i
,
j
−
1
]
)
+
A
[
i
,
j
]
否
则
f[i,j]=\begin{cases} 0\ \ \ \ \ i=N+1或j=0\\ \max(f[i+1,j],f[i,j-1] ) +A[i,j] \ \ \ \ \ 否则\\ \end{cases}
f[i,j]={0 i=N+1或j=0max(f[i+1,j],f[i,j−1])+A[i,j] 否则
核心代码:
for (int i = n; i>0; i--){
for (int j = 1; j < n + 1; j++)
f[i][j] = max(f[i + 1][j], f[i][j - 1])+v[i][j];
}
最长公共子序列
描述:
假设有
x
=
<
x
1
,
x
2
.
.
x
m
>
x=<x_1,x_2..x_m>
x=<x1,x2..xm>,
y
=
<
y
1
,
y
2
.
.
y
n
>
y=<y_1,y_2..y_n>
y=<y1,y2..yn>两个序列,求最长公共子序列,不要求连续。
思路:
依次截取两序列的前面i,j部分,则这连个子序列的最长公共子序列长度为:
f
[
i
,
j
]
=
{
0
i
=
0
或
j
=
0
f
[
i
−
1
,
j
−
1
]
+
1
i
,
j
>
0
且
x
i
=
y
j
max
(
f
[
i
,
j
−
1
]
,
f
[
i
−
1
,
j
]
)
i
,
j
>
0
且
x
i
≠
y
j
f[i,j]=\begin{cases} 0\ \ \ \ \ i=0或j=0\\ f[i-1,j-1]+1 \ \ \ \ \ i,j>0且x_i=y_j\\ \max(f[i,j-1],f[i-1,j] ) \ \ \ \ \ i,j>0且x_i \neq y_j\\ \end{cases}
f[i,j]=⎩⎪⎨⎪⎧0 i=0或j=0f[i−1,j−1]+1 i,j>0且xi=yjmax(f[i,j−1],f[i−1,j]) i,j>0且xi̸=yj
计算两个字符串的编辑距离
描述
把两个字符串变成相同的三个基本操作定义如下:
- 修改一个字符(如把a 变成b)
- 增加一个字符(如abed 变成abedd)
- 删除一个字符(如jackbllog 变成jackblog)
针对于jackbllog 到jackblog 只需要删除一个或增加一个l 就可以把两个字符串变为相同。把这种操作需要的最小次数定义为两个字符串的编辑距离L。
编写程序计算指定文件中字符串的距离。输入两个长度不超过512 字节的ASCII 字符串,在屏幕上输出字符串的编辑距离。
输入样例
Hello world!
Hello wortd!
输出样例
1
#include <vector>
#include <string>
#include <iostream>、
#include <algorithm>
#include <queue>
using namespace std;
vector<vector<int>> res;
void dis(string a, string b)
{
for (int i = 1; i < a.size(); i++) {
for (int j = 1; j < b.size(); j++) {
if (a[i] == b[j])
res[i][j] = res[i - 1][i - 1];
else {
res[i][j] = min(min(res[i-1][j-1]+1,res[i][j-1]+1), res[i - 1][j] + 1);
}
}
}
cout << res[a.size()-1][b.size()-1];
}
int main()
{
string str1 = "hllo0";
string str2 = "hlo0";
str1.insert(str1.begin(), '1');
str2.insert(0, "1");
for (int i = 0; i < str1.size(); i++)
res.push_back(vector<int>(str2.size(),0));
for (int i = 0; i < str2.size(); i++)
res[0][i] = i;
for (int i = 0; i < str1.size(); i++)
res[i][0] = i;
dis(str1,str2);
system("pause");
return 0;
}
例题 hero shoot eagle
描述
输入一个序列,求其最长下降子序列长度及内容。
思路
一般这种求最长子序列的问题,可以考虑转化为公共子序列的问题。本题中,将原序列A降序排序得到序列B,求序列A B的最长公共子序列即可。
代码
#include "stdafx.h"
#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
//递归,
void print_lcs(vector<vector<int>> &v, vector<int> &a, vector<int> &b, int i, int j)
{
if (i == 0 || j == 0)
return;
if (a[i - 1] == b[j - 1]){
print_lcs(v,a,b,i-1,j-1);
cout << a[i - 1] << " ";
}
else if (v[i - 1][j] > v[i][j - 1])
print_lcs(v, a, b, i - 1, j);
else
print_lcs(v, a, b, i, j - 1);
}
int main()
{
int n;
cin >> n;
vector<vector<int>> v;
vector<int> a;
for (int i = 0; i < n+2; i++){
vector<int> tmp(n+2, 0);
v.push_back(tmp);
}
int tmp;
for (int i = 1; i < n+1; i++){
cin >> tmp;
a.push_back(tmp);
}
vector<int> b(a);
sort(b.begin(),b.end(),greater<int>());//降序排序
for (int i = 1; i<n+1; i++){
for (int j = 1; j < n + 1; j++){
if (a[i-1] == b[j-1])
v[i][j] = v[i - 1][j - 1] + 1;
else if (v[i - 1][j]>v[i][j - 1])
v[i][j] = v[i - 1][j];
else
v[i][j] = v[i][j - 1];
}
}
cout << v[n][n]<< endl;
print_lcs(v, a, b, n, n);
system("pause");
return 0;
}
背包问题
0-1背包问题
描述
输入:物品重量向量w,物品价值向量v,背包承受重量C。
输出:是否装物品向量x。
思路
找到最优子结构:
m
[
i
,
j
]
=
{
0
,
i
=
0
或
j
=
0
m
[
i
−
1
,
j
]
,
i
>
0
且
w
i
>
j
max
(
v
i
+
m
[
i
−
1
,
j
−
w
i
]
,
m
[
i
−
1
,
j
]
)
,
i
>
0
且
w
i
≤
j
m[i,j]=\begin{cases} 0, \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ i=0或j=0\\ m[i-1,j], \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ i>0且w_i>j\\ \max(v_i+m[i-1,j-w_i], m[i-1, j]), i>0且w_i \leq j\\ \end{cases}
m[i,j]=⎩⎪⎨⎪⎧0, i=0或j=0m[i−1,j], i>0且wi>jmax(vi+m[i−1,j−wi],m[i−1,j]),i>0且wi≤j
意思是,新增一个物品,看物品是不是比总重量大,如果比总重量小,那就要判断是放好还是不放好,如果放的话,就要知道除了这个物品重量,能放的最大价值(
m
[
i
−
1
,
j
−
w
i
]
m[i-1,j-w_i]
m[i−1,j−wi]),再加上这个物品的价值,合起来的总价值,与不放这个物品的总价值(
m
[
i
−
1
,
j
]
m[i-1, j]
m[i−1,j])比较大小。
同样也将0 0 作为起始状态,也就是矩阵最左边和最上面的行列都是0,表示一个边界。
示例代码
#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
//输出放入背包的物品序号
void print_items(vector<vector<int>> &res, vector<int> w, int c)
{
int j = c;
for (int i = w.size() - 1; i > 0; i--){
if (res[i][j] != res[i - 1][j]){
j -= w[i];
cout << i<<" ";
}
}
}
int main()
{
vector<int> w = { 0, 2, 3, 4, 5 };//4个物品
vector<int> v = { 0, 3, 4, 5, 7 };//4个物品价值
int c = 9;//背包承重
vector<vector<int>> res;
for (int i = 0; i < 5; i++){
vector<int> tmp(10, 0);
res.push_back(tmp);
}
for (int i = 1; i <= 4; i++){
for (int j = 1; j <=9 ; j++){
if (w[i]>j)
res[i][j] = res[i - 1][j];
else{
res[i][j] = max(v[i] + res[i - 1][j - w[i]], res[i - 1][j]);
}
}
}
cout << res[4][9]<<endl;
print_items(res, w, 9);
return 0;
}
一般背包问题
在一般背包问题中,物品的数量不受限制,这意味着一件物品可以不止放入背包一次。那么,如何将其转换为0-1型背包问题呢?
- 可以对每件物品,计算该物品最大能放几个,比如n个,那么可以新增n-1个物品,每个物品的价值为v,2v,3v…nv,然后按照0-1背包问题来解。
- 采用累加的形式,即 d p [ i ] [ s u m ] = d p [ i − 1 ] [ s u m − 0 ∗ V m ] + d p [ i − 1 ] [ s u m − 1 ∗ V m ] + d p [ i − 1 ] [ s u m − 2 ∗ V m ] + . . . + d p [ i − 1 ] [ s u m − K ∗ V m ] ; 其 中 K = s u m / V m dp[i][sum] = dp[i-1][sum - 0*Vm] + dp[i-1][sum - 1*Vm]+ dp[i-1][sum - 2*Vm] + ... + dp[i-1][sum - K*Vm]; 其中K = sum / Vm dp[i][sum]=dp[i−1][sum−0∗Vm]+dp[i−1][sum−1∗Vm]+dp[i−1][sum−2∗Vm]+...+dp[i−1][sum−K∗Vm];其中K=sum/Vm.
放硬币问题
题目
要注意的是当只能用一种硬币时,种数都是1。
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int n;
int coin[6] = {0, 1, 5, 10, 25, 50 };
vector<vector<int>> dp;
for (int i = 0; i <= 5; i++)
dp.push_back(vector<int>(7491, 0));
for (int i = 1; i <= 5; i++)
dp[i][0] = 1;
for (int i = 1; i <= 7490; i++)
dp[1][i] = 1;
for (int i = 2; i <= 5; i++){
for (int j = 1; j <= 7490; j++){
dp[i][j] = dp[i - 1][j];
if (coin[i] <= j){
int k = 1;
while (j - k*coin[i] >= 0){
dp[i][j] += (dp[i - 1][j - k*coin[i]]);
k++;
}
}
}
}
while (cin >> n)
cout << dp[5][n] << endl;
return 0;
}
硬币为浮点数
题目
大意就是,硬币可以是浮点数,就要把它转为整数。
要注意
- 用longlong保存数值,不然会出错。(long long 最长19位)可以用最大值300自己试试看,结果为负的话肯定就有问题。
- 格式化输出的方式,
setprecision
等
#include <vector>
#include <iostream>
#include <algorithm>
#include <iomanip>
using namespace std;
int main()
{
int coin[12] = {0, 1, 2, 4, 10, 20, 40, 100, 200, 400, 1000, 2000 };//除了5
vector<vector<long long>> dp;
for (int i = 0; i <= 12; i++)
dp.push_back(vector<long long>(6000+1, 0));
for (int i = 1; i <= 12; i++)
dp[i][0] = 1;
for (int i = 1; i <= 6000;i++)
dp[1][i] = 1;
for (int i = 2; i <= 12; i++){
for (int j = 1; j <= 6000; j++){
dp[i][j] = dp[i - 1][j];
if (coin[i] <= j){
int k = 1;
while (j - k*coin[i] >= 0){
dp[i][j] += (dp[i - 1][j - k*coin[i]]);
k++;
}
}
}
}
float n;
while (cin >> n){
if (n == 0.00)
break;
int a = int(n * 20);
cout << setprecision(2) << fixed <<setw(6)<< n << setw(17) << dp[12][a] << endl;
}
return 0;
}