概括
所谓动态规划,其实和贪心有相似之处,都是通过某种方法去求最优解。
但是,与贪心不同,贪心是通过求每一步的最优解,从而达到全局最优解。
而动态规划是,一步一步演算,列出所有的解,最后从所有的解中挑选出最优解。
例题
给定 N (1 <= N <= 100000) 个绝对值不大于 1000 的整数(可能为负数)组成的序列
a[1],a[2],a[3],…,a[N],
求从该序列中取出连续一个子段,使这个子段的和最大。
如果某子序列全是负数则定义该子段和为 0。求最大连续子段和。
对于这个问题我们可以这样来
设计两个数组,一个保存序列,另一个保存以这个元素为最后一个元素的子段的和。
于是我们可以的到状态转移方程:dp[i] =max{dp[i-1] + a[i], 0}
上面方程的意思是,到本元素的之前的子段的和等于到之前那个元素的子段的和加上这个元素
同时,如果和小于0,会导致子段和减小,那时候就直接取0;
下面是具体的转移过程
int maxSubSum = 0;
dp[1] = Max(a[1], 0);
int i;
for (i = 2; i <= N; ++i)
dp[i] = Max(dp[i - 1] + a[i], 0);
for (i = 1; i <= N; ++i) {
if (maxSubSum < dp[i])
maxSubSum = dp[i];
}
另一个问题
有一只经过训练的蜜蜂只能爬向右侧相邻的蜂房,不能反向爬行。
请编程计算蜜蜂从蜂房a爬到蜂房b的可能路线数。
1 3 5 .....
2 4 6 .....
对这个问题来说,很简单,只需要从a开始,逐个往由推,同时用数组记录线路数量就行。
举个例子,从3到6,首先,给a[3]赋值1,a[2]赋值0,那么4只有从3走过来一条线路。
那么a[4]记作1.接下来a[5],可以从3过来,可以从4过来,是两条线路。那么接下来是6,6的话,走4只有一条线路,走5有两条线路,所以一共有三条线路。
由此,我们得到状态转移方程:a[i]=a[i-1]+a[i-2]。
于是我们可以写出程序
#include<cmath>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
ll v[1000000];
int main()
{
int n;
scanf("%d",&n);
while(n--)
{
int a,b;
scanf("%d %d",&a,&b);
memset(v,0,sizeof(v));
v[a]=1;
for(int i=a+1;i<=b;i++)
{
v[i]=v[i-1]+v[i-2];
}
cout<<v[b]<<endl;
}
return 0;
}
总结
经过这几日做题,我已经对动态规划有了个大致的了解,动态规划的核心是递推关系式,或者说,动态规划其实和递推差不多。
用动态规划解题必须先找出题目的递推方程。如果搞不出方程,题目将会变得无比困难。