动态规划

11 篇文章 0 订阅
8 篇文章 0 订阅

动态规划

一 般解法:

  1. 利用最优子结构定义一个关于解的目标值的递归方程,采用“自底向上”而非使用递归时的“自顶向下”。
  2. 将每个子问题的解保留,需要时可以查找。

走格子问题

问题: 从左下角走到右上角,每步只能往上、右方向,每个格子有值,求到终点值最大。

思路:
假设矩阵为如下形式:
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+1j=0max(f[i+1,j],f[i,j1])+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 = &lt; x 1 , x 2 . . x m &gt; x=&lt;x_1,x_2..x_m&gt; x=<x1,x2..xm>, y = &lt; y 1 , y 2 . . y n &gt; y=&lt;y_1,y_2..y_n&gt; y=<y1,y2..yn>两个序列,求最长公共子序列,不要求连续。

思路:
依次截取两序列的前面i,j部分,则这连个子序列的最长公共子序列长度为:
f [ i , j ] = { 0       i = 0 或 j = 0 f [ i − 1 , j − 1 ] + 1       i , j &gt; 0 且 x i = y j max ⁡ ( f [ i , j − 1 ] , f [ i − 1 , j ] )       i , j &gt; 0 且 x i ≠ y j f[i,j]=\begin{cases} 0\ \ \ \ \ i=0或j=0\\ f[i-1,j-1]+1 \ \ \ \ \ i,j&gt;0且x_i=y_j\\ \max(f[i,j-1],f[i-1,j] ) \ \ \ \ \ i,j&gt;0且x_i \neq y_j\\ \end{cases} f[i,j]=0     i=0j=0f[i1,j1]+1     i,j>0xi=yjmax(f[i,j1],f[i1,j])     i,j>0xi̸=yj

计算两个字符串的编辑距离

描述
把两个字符串变成相同的三个基本操作定义如下:

  1. 修改一个字符(如把a 变成b)
  2. 增加一个字符(如abed 变成abedd)
  3. 删除一个字符(如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 &gt; 0 且 w i &gt; j max ⁡ ( v i + m [ i − 1 , j − w i ] , m [ i − 1 , j ] ) , i &gt; 0 且 w i ≤ j m[i,j]=\begin{cases} 0, \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ i=0或j=0\\ m[i-1,j], \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ i&gt;0且w_i&gt;j\\ \max(v_i+m[i-1,j-w_i], m[i-1, j]), i&gt;0且w_i \leq j\\ \end{cases} m[i,j]=0,                                                             i=0j=0m[i1,j],                                              i>0wi>jmax(vi+m[i1,jwi],m[i1,j]),i>0wij
意思是,新增一个物品,看物品是不是比总重量大,如果比总重量小,那就要判断是放好还是不放好,如果放的话,就要知道除了这个物品重量,能放的最大价值( m [ i − 1 , j − w i ] m[i-1,j-w_i] m[i1,jwi]),再加上这个物品的价值,合起来的总价值,与不放这个物品的总价值( m [ i − 1 , j ] m[i-1, j] m[i1,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型背包问题呢?

  1. 可以对每件物品,计算该物品最大能放几个,比如n个,那么可以新增n-1个物品,每个物品的价值为v,2v,3v…nv,然后按照0-1背包问题来解。
  2. 采用累加的形式,即 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[i1][sum0Vm]+dp[i1][sum1Vm]+dp[i1][sum2Vm]+...+dp[i1][sumKVm];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;
}
硬币为浮点数

题目
大意就是,硬币可以是浮点数,就要把它转为整数。
要注意

  1. 用longlong保存数值,不然会出错。(long long 最长19位)可以用最大值300自己试试看,结果为负的话肯定就有问题。
  2. 格式化输出的方式,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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值