1、什么是动态规划?
动态规划的定义,引自维基百科:
dynamicprogramming is a method for solving a complex problem bybreakingit down into a collection of simpler subproblems.
动态规划是对某类最优化问题的解决方法。动态规划寻找一种对问题的观察角度,构造出问题的子问题,使原问题能够以递推的方式去解决。
2、动态规划原理
适合应用动态规划方法求解的最优化问题应该具备两个要素:最优子结构和子问题重叠。
下面由”求一个数列的最长上升(递增)子数列长度(LIS)”问题来说明:
给定一个长度为6的数列:1 5 6 2 7 8
这个数列的的LIS是1 5 6 7 8,长度为5
1 2 7 8和5 6 7 8同样为数列的子上升数列,但它们都不是最长上升子数列。
最优子结构
用动态规划方法求解最优化问题的第一步是刻画出最优解的结构。如果一个问题的最优解包含其子问题的最优解,就称此问题具有最优子结构性质。
重新定义上述问题:
给定一个长度为N的数列;
设Fk为:以数列第K项结尾的LIS长度;
则F1~N中的最大值即为所求。
对于求解Fk,每个F1~k-1都是Fk的子问题。不难证明,以k项结尾的LIS包含着以1~k-1项结尾的LIS。
需要注意的是,定义最优子结构有个隐含条件,就是子结构的无后效性。从F1_k-1可以求得Fk,也就是每个阶段的最优解可以从之前某个阶段的最优解直接得到,则称这个问题具有最优子结构。
而同时,不管之前某个阶段的最优解是如何得到的,不管F1~k-1是如何得到的,因为要求Fk时,只需知道F1~k-1的结果,而并不关心它是如何求出来的。
子问题重叠
重叠的子问题实际上是同一个问题,只是作为不同问题的子问题出现。通过记录这些重叠的子问题,避免问题的反复求解,从而达到以空间换时间的效果。
3、动态规划的具体实现
题意:
天天酷跑,给一个2*n的格子,初始时在(2,1)这个点上,每次两种操作
从(2,i)移动到(2,i+1)
从(2,i)跳起,经过(1,i+1)(1,i+2)跳到(2,i+3)
当到达(1,n)或者(2,n)的时候,游戏结束,每个格子上有一个分数,游戏总分数就是经过的格子分数之和,求能获得的分数的最大值
Sample:
0 0 1 1 0
2 1 2 0 1答案6
思路:
DP[i]表示,到第i个格子而且在地面时能获得的最高分数,答案就是DP[n]
状态转移:
1.从i-1个格子直接走过来dp[i]← dp[i-1] + score[2][i]
2.从i-3个格子跳过来dp[i]← dp[i-3] + score[1][i-2] + score[1][i-1] + score[2][i]
自顶向下,通过递归实现
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[2][100005];
int d[100005];
int vis[100005];
int dp(int i)
{
if(vis[i]) return d[i];
d[i] = a[1][i];
if(i == 0)
return a[1][0];
if(i <= 2)
d[i] += dp(i-1);
else
{
d[i] += max(dp(i-1) , dp(i-3) + a[0][i-1] + a[0][i-2]);
}
vis[i] = 1;
return d[i];
}
int main()
{
int T, n;
scanf("%d", &T);
while(T--)
{
memset(a, 0, sizeof(a));
scanf("%d", &n);
for(int i=0; i<n; i++)
scanf("%d", &a[0][i]);
for(int i=0; i<n; i++)
scanf("%d", &a[1][i]);
memset(d, 0, sizeof(d));
memset(vis, 0, sizeof(vis));
int t;
if(n==3)
{
t = max(a[0][2]+a[0][1]+a[1][0], a[1][2]+a[1][1]+a[1][0]);
}
else if(n==2)
{
t = max(a[0][1]+a[1][0], a[1][1]+a[1][0]);
}
else if(n == 1)
{
t = a[1][0];
}
else if(n >3)
{
t = max(dp(n-1), dp(n-3) + a[0][n-1] + a[0][n-2]);
t = max(t, dp(n-2) + a[0][n-1]);
}
printf("%d\n", t);
}
return 0;
}
自底向上,通过递推实现:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
#define N 200005
int dp[N], a[2][N];
int main() {
int n, t;
cin >> t;
while (t--) {
cin >> n;
memset(a, 0, sizeof(a));
for (int j = 0; j < 2; j++) for (int i = 1; i <= n; i++) scanf("%d", &a[j][i]);
dp[0] = 0;
for (int i = 1; i <= n + 2; i++) {
dp[i] = dp[i - 1] + a[1][i];
if (i > 3) dp[i] = max(dp[i], dp[i - 3] + a[0][i - 2] + a[0][i - 1] + a[1][i]);
}
printf("%d\n", dp[n + 2]);
}
return 0;
}