动态规划基础入门

什么是动态规划呢?顾名思义,动态地进行规划?让我们先来看个小游戏:


引入:

大家在小的时候或许玩过跳房子的游戏,如果没玩过也没关系,我们对规则进行稍稍改动:


  • 龙龙在跳房子,每次可以选择跳1格或者k个格子,不允许往回跳,请问龙龙从第零格开始跳,跳到第n格一共有多少种方案?

:跳格子的方案与顺序有关,比如先跳一格,再跳两格和先跳两格,再跳一格是不同种方案。
数据范围**:**2<=n<=60; 2<=k<=n


  • 如果你的数学够好,再加上数据范围是较小的,那当然可以用排列组合的思想来解决这道题,但仔细想想也还是挺复杂的,那么这时候就需要用到动态规划的思想了。

  • 我们假设在某一个时刻,龙龙正站在第i个格子,且这个i足够大(大到什么程度呢?只需要比k大就行了,至于原因,看下去就懂了),我们来思考,龙龙是怎么跳到第i个格子的呢?显然,龙龙一定是从第i-1或第i-k个格子跳过来的,那么我们就可以定义一个数组,就暂且称她为dp吧,dp[i]就表示跳到第i个格子一共有多少种方案。

  • 那么根据龙龙一定是从第i-1或第i-k个格子跳过来的,我们就可以列出这样一个方程: dp[i]=dp[i-1]+dp[i-k]
    这个方程非常重要,是解决这道问题的关键,在以后类似的动态规划题目中,我们把她称为状态转移方程。

  • 得到了状态转移方程,那么我们要求第
    dp[i],也就是去求dp[i-1]和dp[i-k],要求dp[i-1]和dp[i-k],也就是去求dp[i-2]、dp[i-1-k]和dp[i-k-1]、dp[i-k-k]……,那么这样一直下去我们就需要知道一个边界条件,来完成我们的“递推思想”
    我们知道,当0<i<k时,龙龙是只能一步一步往前跳的,那么dp[i](0<i<k)时,dp[i]=1,至此,我们有了递推式,又有了初始条件,我们就能解决问题了


递推写法:

#include<stdio.h>
int main()
{
	int n,k;//n表示跳到第n格有多少种方案,k表示每次可以跳一格或者k格
	int dp[100];//dp为状态转移数组
	scanf("%d%d",&n,&k);
	for(int i=0;i<k;i++)
	{
		dp[i]=1;	
	} 
	for(int i=k;i<=n;i++)
	{
		dp[i]=dp[i-1]+dp[i-k];
	}
	printf("%d\n",dp[n]);
	return 0;
}

说完了递推写法,我们再来说说递归写法,递归的一个特点就是在过程中会调用自身,我们给上代码:

#include<stdio.h>
int n,k;//n表示跳到第n格有多少种方案,k表示每次可以跳一格或者k格
int search(int n)
{
	if(n<k)
	return 1;
	else
	return search(n-1)+search(n-k);
}
int main()
{	
	scanf("%d%d",&n,&k);
	printf("%d\n",search(n));
	return 0;
}

看上去似乎也很简单,但其实隐藏了一个很大的“危机”,即使本题的数据范围较小,但还是很容易出现超时的情况,为什么小小的数据会产生超时呢?我们举个菲波那切数列的例子:

  • 所谓斐波那契,和我们的例子很相似,即dp[i]=dp[i-1]+dp[i-2],其中dp[1]=dp[2]=1,那如果运用了递归,那就在算dp[i]的时候,会调用自身,去求前两项,在求前两项时,又会调用自身,再往前求两项,但是这其实是重复计算没有必要的,因为前两项既然已经得到了,说明更之前的一定是已经算过了,那么我们可以用记忆化搜索的方式,给刚才的代码进行一小点处理,来保存之前已经求过的数据,避免大量的重复计算。
#include<stdio.h>
int n,k,dp[100];//n表示跳到第n格有多少种方案,k表示每次可以跳一格或者k格
int search(int n)
{
	if(dp[n]!=0)
	return dp[n];
	if(n<k)
	return dp[n]=1;
	else
	return dp[n]=search(n-1)+search(n-k);
}
int main()
{	
	scanf("%d%d",&n,&k);
	printf("%d\n",search(n));
	return 0;
}

定义

动态规划是一种通过拆分问题(发掘问题子结构),定义问题的状态(可理解为一单值函数)以及状态转移方程使问题能够通过递推(或递归等)的方式化为简单的问题(初值/边界条件)被解决的算法。

性质

  1. 需要满足无后效性,比如说本题的状态方程,一旦dp[i-1]和dp[i-k]为求dp[i]服务完后,就不能对后续的数据再产生影响了。
  2. 问题具有最优子结构性质,这样才能化为子问题进行解题。

难点

  1. 状态
    在复杂问题中,状态可能不是单变量定义,需要用到多维数组,如复杂的背包问题
  2. 状态转移方程
    状态转移方程不仅仅只有四则运算,可能会有积分号,求最大最小,等一系列奇奇怪怪的符号,不方便找
  3. 代码实现
    在oj上要考虑程序的运行速度,内存等问题,在用代码表示自己的思路时可能会出现“我的大脑明白了,但代码表达不了我的情感”的情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值