1、动态规划的定义
动态规划,dynamic Programming,是一种高效解决问题的方法,使用与具有重复子问题和最优子结构的问题。
2、动态规划的思想
动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。动态规划算法多种多样,但它们具有相同的填表格式。
3、动态规划的要素
1. 如果可以把局部子问题的解结合起来得到全局最优解,那这个问题就具备最优子结构 ;
2. 如果计算最优解时需要处理很多相同的问题,那么这个问题就具备重复子问题。
4、动态规划算法的经典应用
(1)背包问题
一个旅行者有一个最多能用M公斤的背包,现在有N件物品,它们的重量分别是W1,W2,…,Wn,它们的价值分别为P1,P2,…,Pn.若每种物品只有一件 在不超过M公斤的前提下,求旅行者能获得最大总价值的方案。
输入格式:
M,N
W1,P1
W2,P2
经过我们粗略的扫描动态规划的定义,我们了解到动态规划问题基本都是通过建立表格,填表格来解决问题的,这里也不例外。首先我们需要确定表格内部单元格的逻辑关系。这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。
用子问题定义状态:即f[i][j]表示前i件物品恰放入一个容量为j的背包可以获得的最大价值。则其状态转移方程便是:
f[i][j] = max{f[i-1][j], f[i-1][j-w[i]]+P[i]}
为什么这里会出现max呢?因为只能从两个状态而来,也就是取和不取当前物品。 我在前i件物品取得重量不超过j的最大价值,是由取不取第i件物品而得到的。对于【将前i件物品放入容量为v的背包中】这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前i-1件物品的问题。如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为的背包中”,价值为f[i-1][j];如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为j-w[i]的背包中”,此时能获得的最大价值就是f[i-1][j-w[i]]再加上通过放入第i件物品获得的价值P[i]。
上面这幅图将f[i][j] = max{f[i-1][j], f[i-1][j-w[i]]+P[i]} 这个式子表现得淋漓尽致..动态规划问题基本都是像这样通过建立表格,填表格来解决问题的。
(2)和为sum的方法数
/*
给定一个有n个正整数的数组A和一个整数sum,求选择数组A中部分数字和为sum的方案数。
当两种选取方案有一个数字的下标不一样,我们就认为是不同的组成方案。
输入描述:
输入为两行:
第一行为两个正整数n(1 ≤ n ≤ 1000),sum(1 ≤ sum ≤ 1000)
第二行为n个正整数Ai,以空格隔开。
输出描述:
输出所求的方案数
输入例子1:
5 15 5 5 10 2 3
输出例子1:
4
dp解决:
以每个物品作为横轴,以背包容量作为纵轴
0 1 2 3 4 5 6..........
0 1 0 0 0 0 0 0..........
5 1 0 0 0 0 1 0
其中1表示前n件物品放入容量为M的背包有1种方法,
(5,0)表示重量为5的物品放入容量为0的背包的背包有1种方法,即不放入。
0表示恰好放满背包的方法为0
当M > weight[i]时,
dp[M] = dp[M] + dp[M-weight[i]];
意义是:放入物品i和不放入物品i的方法总和
*/
#include <bits/stdc++.h>
using namespace std;
int sum_fun(vector<int> &arr, int &n, int &m)
{
int dp[1000][1000];
dp[0][0] = 1;
for (int j = 1; j <= m; ++j)
dp[0][j] = 0;
for (int i = 1; i <= n; ++i)
{
dp[i][0] = 1;
for (int j = 0; j <= m; ++j)
{
if (j < arr[i])
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - arr[i]];
}
}
return dp[n][m];
}
int main(int argc, char const *argv[])
{
int n, m, k;
vector<int> arr = {0};
cin >> n >> m;
for(int i = 0; i < n; i++)
{
cin >> k;
arr.push_back(k);
}
cout << sum_fun(arr, n, m);
system("pause");
return 0;
}
(3)最长公共子序列
/*
假设C=<z1,z2,...,zk>是X与Y的LCS, 我们观察到
如果Xm=Yn,则Ck=Xm=Yn,有Ck−1是Xm−1与Yn−1的LCS;
如果Xm≠Yn,则Ck是Xm与Yn−1的LCS,或者是Xm−1与Yn的LCS。
*/
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int c[5001][5001];//函数内不能申请太大的数组,所以放在全局存储区
int lcs(const string &str1, const string &str2)
{
int s1 = str1.size();
int s2 = str2.size();
//初始状态
for (int i = 0; i <= s1; ++i)
c[i][0] = 0;
for (int j = 0; j <= s1; ++j)
c[0][j] = 0;
//状态转移方程
for (int i = 1; i <= s1; ++i)
{
for (int j = 1; j <= s2; ++j)
{
if (str1[i - 1] == str2[j - 1])
{
c[i][j] = c[i - 1][j - 1] + 1;
}
else
{
c[i][j] = max(c[i - 1][j], c[i][j - 1]);
}
}
}
return c[s1][s2];
}
int main(int argc, char const *argv[])
{
string s1, s2;
getline(cin, s1);
getline(cin, s2);
cout << lcs(s1, s2);
return 0;
}
(4)连续最大和
/*
题目:一个数组有 N 个元素,求连续子数组的最大和。 例如:[-1,2,1],和最大的连续子数组为[2,1],其和为 3
输入描述:
输入为两行。
第一行一个整数n(1 <= n <= 100000),表示一共有n个元素
第二行为n个数,即每个元素,每个整数都在32位int范围内。以空格分隔。
输出描述:
所有连续子数组中和最大的值。
输入例子:
3
-1 2 1
输出例子:
3
*/
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(int argc, char const *argv[])
{
int i, j, s, n;
cin >> n;
vector<int> a;
vector<int> dp;
for (i = 1; i <= n; ++i)
{
cin >> j;
a.push_back(j);
}
dp.push_back(a[0]);
for (i = 1; i < n; ++i)
{
//连续最大和中是否包含当前值,两者取最大的那个
dp.push_back(max(dp[i-1] + a[i], a[i]));
}
int sum = *max_element(dp.begin(), dp.end());
cout << sum;
system("pause");
}
参考:http://www.cnblogs.com/Creator/archive/2011/05/17/2048302.html