经典算法之动态规划(Dynamic Programming)

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~青萍之末~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值