动态规划概述(1)

动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优解的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。具体的动态规划算法多种多样,但它们具有相同的填表格式。

还有几个重要的名词:

1.无后效性,可能很多不懂的小伙伴会在这里直接崩溃其实很简单,举个生活中不存在的例子:假设你现在在干活,而这时有一个人穿越到了过去,把你刚刚犁的田都踩了一遍,但是对你来说没有影响,因为你的菜已经丰收了,所以你就可以不去收拾那个恶搞的人了。这不恰当的例子看懂的可以直接看下一名词。没看太懂的呢可以看看术语性的定义:在进行动态规划的过程中的任何一步,都是对前面的总结。计算过去的步骤都是无效的,是不需要考虑的,但是是正确的,是向正确的解靠拢的。这个就是后无效性。

2.最优子结构,这点学过算法的应该都知道吧,大致的意思就是一个大问题分成若干个小的子问题,当我们先去解决小问题的时候,为了使最终合并成的大问题最优,自己每一个都要达到一定的最优性特征。贪心的最优子结构比较简单,只需要管自己就行了,后面的不用担心自己就会变成最优结构,但动态规划每走一步都要考虑全局,所以有很多问题贪心拿不到满分,但dp能够解决。

下面开始:

弄个例子可能就明白了:

背包问题/最大子段和

背包问题:

题目传送门

题目描述:

一个旅行者有一个最多能装 MM 公斤的背包,现在有 nn 件物品,它们的重量分别是W1,W2,...,WnW1,W2,...,Wn,它们的价值分别为C1,C2,...,CnC1,C2,...,Cn,求旅行者能获得最大总价值。

【输入】

第一行:两个整数,MM(背包容量,M≤200M≤200)和NN(物品数量,N≤30N≤30);

第2..N+12..N+1行:每行二个整数WiCiWi,Ci,表示每个物品的重量和价值。

【输出】

仅一行,一个数,表示最大总价值。

【输入样例】

10 4
2 1
3 3
4 5
7 9

【输出样例】

12

这道题想必应该都知道/会做/做过吧

所以就只提供思想啦(其实程序没几行,也黏上)

1) 把背包问题抽象化(X1,X2,…,Xn,其中 Xi 取0或1,表示第 i 个物品选或不选),Vi表示第 i 个物品的价值,Wi表示第 i 个物品的体积(重量);

2) 建立模型,即求max(V1X1+V2X2+…+VnXn);

3) 约束条件,W1X1+W2X2+…+WnXn<capacity;

3) 定义V(i,j):当前背包容量 j,前 i 个物品最佳组合对应的价值;

4) 最优性原理是动态规划的基础,最优性原理是指“多阶段决策过程的最优决策序列具有这样的性质:不论初始状态和初始决策如何,对于前面决策所造成的某一状态而言,其后各阶段的决策序列必须构成最优策略”。判断该问题是否满足最优性原理,采用反证法证明:假设(X1,X2,…,Xn)是01背包问题的最优解,则有(X2,X3,…,Xn)是其子问题的最优解,假设(Y2,Y3,…,Yn)是上述问题的子问题最优解,则理应有(V2Y2+V3Y3+…+VnYn)+V1X1 > (V2X2+V3X3+…+VnXn)+V1X1;而(V2X2+V3X3+…+VnXn)+V1X1=(V1X1+V2X2+…+VnXn),则有(V2Y2+V3Y3+…+VnYn)+V1X1 > (V1X1+V2X2+…+VnXn);该式子说明(X1,Y2,Y3,…,Yn)才是该01背包问题的最优解,这与最开始的假设(X1,X2,…,Xn)是01背包问题的最优解相矛盾,故01背包问题满足最优性原理; 5) 寻找递推关系式,面对当前商品有两种可能性: 第一,包的容量比该商品体积小,装不下,此时的价值与前i-1个的价值是一样的,即V(i,j)=V(i-1,j) 第二,还有足够的容量可以装该商品,但装了也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即V(i,j)=max{ V(i-1,j),V(i-1,j-w(i))+v(i) }其中V(i-1,j)表示不装,V(i-1,j-w(i))+v(i) 表示装了第i个商品,背包容量减少w(i)但价值增加了v(i); 由此可以得出递推关系式: 1) j<w(i)      V(i,j)=V(i-1,j) 2) j>=w(i)     V(i,j)=max{ V(i-1,j),V(i-1,j-w(i))+v(i) } g) 填表,首先初始化边界条件,V(0,j)=V(i,0)=0;。

还有代码

#include<cstdio>
using namespace std;
const int maxm = 201, maxn = 31;
int m, n;
int w[maxn], c[maxn];
int f[maxn][maxm]; 
//求x和y最大值
int max(int x,int y) {return x>y?x:y;}
使用二维数组存储各子问题时方便,但当maxn和maxm较大时,如maxn、maxm=10000时不能定义
二维数组f,怎么办,其实可以用一维数组。
int main(){
    scanf("%d%d",&m, &n); //背包容量m和物品数量n
    for (int i = 1; i <= n; i++) //在初始化循环变量部分,定义一个变量并初始化
    scanf("%d%d",&w[i],&c[i]); //每个物品的重量和价值
    for (int i = 1; i <= n; i++) // f[i][v]表示前i件物品,总重量不超过v的最优价值
        for (int v = m; v > 0; v--)
            if (w[i] <= v) f[i][v] = max(f[i-1][v],f[i-1][v-w[i]]+c[i]);
            else f[i][v] = f[i-1][v];
    printf("%d",f[n][m]); // f[n][m]为最优解
    return 0;
}

有兴趣的可以改成一维的。

下面将另一道最最最单纯的dp题:最大子段和。

题目传送门

题目描述

给出一段序列,选出其中连续且非空的一段使得这段和最大。

输入格式

第一行是一个正整数NNN,表示了序列的长度。

第二行包含NNN个绝对值不大于100001000010000的整数AiA_iAi​,描述了这段序列。

输出格式

一个整数,为最大的子段和是多少。子段的最小长度为111。

输入输出样例

输入 #1复制

7
2 -4 3 -1 2 -4 3

输出 #1复制

4

说明/提示

【样例说明】

2,−4,3,−1,2,−4,32,-4,3,-1,2,-4,32,−4,3,−1,2,−4,3中,最大的子段和为4,该子段为3,−1,23,-1,23,−1,2.

【数据规模与约定】

对于40%40\%40%的数据,有N≤2000N ≤ 2000N≤2000。

对于100%100\%100%的数据,有N≤200000N ≤ 200000N≤200000

解法:

首先,通过题意,我们可以了解到:

f[i]=max(f[i-1]+n[i],n[i])

但是!

f[n]的值并不一定是最终结果

比如这个输入:(Hack and 78)

5 233 233 -666 1 1

如果直接输出f[n]的值,结果会是2,但是答案应该为466!

为什么?

因为若f[i]的值为负数,则f[i+1]的值就是n[i],而n[i]的值不一定比前面的最大字段和数大!

(或者n[i]为负数,则f[i]小于f[i-1]!)

所以,我们还要再用一个数从1到n再查找一次,才能找出最大数!!!

and code:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n[200001],p,ans[200001]={0};
    int sum=-9999999;//|x|<=10000   QWQ
    cin>>p;
    for(int i=1;i<=p;i++)
    {
        cin>>n[i];//输入
        ans[i]=max(ans[i-1]+n[i],n[i]);//DP
        sum=max(sum,ans[i]);//取最大值也同时进行,节约时间
    }
    cout<<sum;//直接输出
    return 0;
}

那么动态规划基础就完毕啦,还有提供两个题库(应该都知道)

就是上面两个题目的题目传送门

886

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值