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