简单的动态规划

谈到动态规划,首先说明关于动态规划的几个术语:阶段,状态,无后效性,决策,策略,状态转移方程

阶段:把求解问题的过程恰当地拆分成若干个相互联系的阶段,以便于求解。
状态:每一个阶段开始的时候处于的处境(也称作不可控因素)。状态是上一阶段的终点,也是下一阶段的起点,过程的每一实现都可以用一状态序列表示,一个阶段可以有很多个状态。
无后效性:给定一个状态,则在此阶段之后的过程发展与此阶段之前没有任何关系,这就意味着过程的历史只能通过当前的状态去影响它的未来的发展,这个性质就称为无后效性。
决策:一个阶段的状态给定以后,从该状态演变到下一个阶段的某个状态的一种选择称为决策,也称为控制。
策略:每个阶段的决策组成的序列称为策略。
状态转移方程:f(k+1) = f (k) + opt(k)

f(k)指第k个阶段所处于的状态
opt(k) 指第k个阶段做出的决策
f(k+1)值第k+1个阶段所处于的状态

使用动态规划的条件:

  1. 满足最优化原理:一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质。
  2. 满足无后效性。

动态规划的基本模型:

(1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。
(2)确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。
(3)确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出。但事实上常常是反过来做,根据相邻两段各状态之间的关系来确定决策。
(4)寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
状态转移方程一般形式:
顺推:
f[Uk] = f[Uk-1] + L[Uk-1,Xk-1] ;
L[Uk-1,Xk-1] 表示从状态f[Uk-1]到状态f[Uk]的决策,也可以称为费用函数
倒推:
f[Uk] = f[Uk+1] - L[UK+1,Xk-1];
L[Uk+1,Xk+1] 表示从状态f[Uk]到状态f[Uk+1]的决策。

题目讲解

题目1:过河卒(源自洛谷)
首先说明:本题本人刚开始也不会,这个解析是学习答案解析中某位码友的分析从而有了自己的收获,见过河卒解析
解析:
先看所给的图例(从特殊归纳到一般)
决策对象就是在给定的表格上实现从A到B,对于6*6的方格,先不考虑马的位置和马能够到的位置,这样就可以划分为6个阶段,很容易发现各个阶段状态变量的个数,我们可以这样想,题目让计算最后有几条路径能从A到B,那我们把每个点有几条路径经过都算出来即可,由于卒每次只能往下或者右移动一格,很容易得到状态转移方程:
我们把棋盘看做一个二维数组得到:
f[i][j] = f[i-1][j] + f[i][j-1]
即经过f[i][j]位置的路径数量等于经过其上位置和其右位置的路径数量之和,这就是本题的状态转移方程。
现在考虑马的位置和马能到达的位置(以下均称马位),我们可以用一个相同容量的bool二维数组来储存棋盘的状态,马位为true,当循环轮到到马位的时候直接跳过,这样就要保证马位上的初始值都是0,这样才能不影响之后的位置。
然后就可以得到以下代码:

//最重要的是找到状态转移方程! 
#include<iostream>
#define ll unsigned long long int 
using namespace std ;
int main()
{
	const int a[9] = {1,2,1,2,-1,-2,-1,-2,0} ;
	const int b[9] = {2,1,-2,-1,2,1,-2,-1,0} ;
	int m,n,x,y;
	cin >> m >> n >> x >> y ;
	m += 2 ; n += 2 ; x += 2 ; y += 2 ;//+2的原因是防止数组越界,因为马的位置未给出,根据马能到达的位置使得整体位置横纵都+2。
	ll array[30][30] = {0} ;//注意初始化
	bool array1[30][30] = {0};//注意初始化,只有=0的时候才能这样初始化,也可以把这个弄成全局变量,这样系统会自动把每一个元素都初始化为0
	array[1][2] = 1 ;//初始化,保证array[2][2]的初始值是1
	//记录马的位置,采用数组的形式也是一个好的技巧
	for(int i = 0 ; i < 9 ; i ++ )
	{
		array1[x+a[i]][y+b[i]] = 1 ;
	}
	//利用循环算出经过每一个位置的路径数
	for(int i = 2 ; i <= m ; i ++ )
	{
		for ( int j = 2 ; j <= n ; j ++ )
		{
			if(array1[i][j]) continue ;
			array[i][j] =   array[i-1][j] + array[i][j-1]  ;
		}
	}
	cout << array[m][n] ;
	return 0 ; 
} 

上面的方法需要的空间较多,因为我们储存了大量的中间结果,我们并不需要经过每一个位置的路径数,我们只需要知道经过B点的路径数,那么我们改如何节约空间呢?
观察状态转移方程:
f[i][j] = f[i-1][j] + f[i][j-1]
每一个位置都进行着相同的操作,都是左边位置的路径数与上边位置的路径数之和,我们不妨引入位运算
这样状态转移方程可以写成:
f[i&1][j] = f[(i-1)&1][j] + f[i&1][j-1]
因为实数i&1只有两个值0与1,这样交替使用,就达到了目的
同样注意初始化,为了使得array[2&1][2]为1,让array[1][2] = 1 ; 或者array[0][1]= 1 ;
这样只需要建造二维数组array[2][30]即可

//最重要的是找到状态转移方程! 
#include<iostream>
#define ll unsigned long long int 
using namespace std ;
int main()
{
	const int a[9] = {1,2,1,2,-1,-2,-1,-2,0} ;
	const int b[9] = {2,1,-2,-1,2,1,-2,-1,0} ;
	int m,n,x,y;
	cin >> m >> n >> x >> y ;
	m += 2 ; n += 2 ; x += 2 ; y += 2 ;
	ll array[2][30] = {0} ;
	bool array1[30][30] = { 0 };
	array[1][2] = 1 ;
	for(int i = 0 ; i < 9 ; i ++ )
	{
		array1[x+a[i]][y+b[i]] = 1 ;
	}
	for(int i = 2 ; i <=  m ; i ++ )
	{
		for ( int j = 2 ; j <= n ; j ++ )
		{
			if(array1[i][j])
			{	
				array[i&1][j] = 0 ; // 注意要覆盖掉原来的值 
				continue ; 
			}
			//这里体现状态转移方程 
			array[i&1][j] =   array[(i-1)&1][j] + array[i&1][j-1]  ;
		}
	}
	cout << array[m&1][n] ;
	return 0 ; 
} 

我们想一下能否继续减少空间消耗,
注意到f[i][j] = f[i-1][j] + f[i][j-1] 与我们的循环,f[i-1][j]的值就是上一次外层循环的f[i][j],而上一层的f[i][j]也是中间值,我们可以舍弃掉,因此可以再次改成一维数组,状态转移方程为:
f[j] = f[j] + f[j-1] ;第二个的f[j]就是上一次的结果,储存的即为本位置的上边的位置的路径数,f[j-1]即为左边位置的路径数。
这样就可以把二维数组变为一维数组,进一步节省了空间

//最重要的是找到状态转移方程! 
#include<iostream>
#define ll unsigned long long int 
using namespace std ;
int main()
{
	const int a[9] = {1,2,1,2,-1,-2,-1,-2,0} ;
	const int b[9] = {2,1,-2,-1,2,1,-2,-1,0} ;
	int m,n,x,y;
	cin >> m >> n >> x >> y ;
	m += 2 ; n += 2 ; x += 2 ; y += 2 ;
	ll array[30] = {0} ;
	bool array1[30][30] = {0};
	array[2] = 1 ;
	for(int i = 0 ; i < 9 ; i ++ )
	{
		array1[x+a[i]][y+b[i]] = 1 ;
	}
	for(int i = 2 ; i <= m ; i ++ )
	{
		for ( int j = 2 ; j <= n ; j ++ )
		{
			if(array1[i][j])
			{	
				array[j] = 0 ; // 注意要覆盖掉原来的值 
				continue ; 
			}
			//这里体现状态转移方程 
			array[j] += array[j-1] ;
		}
	}
	cout << array[n] ;
	return 0 ; 
} 

本文学习于:
1.动态规划——百度百科
2.最优化原理——百度百科
3.洛谷试炼场

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.Toser

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值