dp动态规划-分类型(题目总结)

常见的动态规划类型

坐标型动态规划(20%) 重点
序列型动态规划(20%) 重点
划分型动态规划(20%) 重点
区间型动态规划(15%) 重点
背包型动态规划(10%)
最长序列型动态规划(5%)
博弈型动态规划(5%)
综合性动态规划(5%)

坐标型动态规划

一、机器人移动

题目描述:
给定m行n列的网络,有一个机器人从左上角(0,0)出发,每一步可以向下或者向右走一步
网络中有些地方有障碍,机器人不能通过障碍格,障碍格处用数字1表示,无障碍的用0表示
问有多少种不同的方式走到右下角
解决方案:
1.题目是问种数的坐标型动态规划
2.状态设计:
最后一步:
最后一步一定是从左边(i,j-1) 或上边(i-1,j)过来的
子问题:
除去最后一步,问题变为从左上角走到(i,j-1)和(i-1,j)有几种方式

则设置状态 f[i][j]为从左上角有多少种方式走到格子(i,j)

3.状态转移方程:
f[i][j] = f[i-1][j] + f[i][j-1]

4.初始条件:
f[0][0] = 0
若左上角(0,0) 或者右下角(m-1,n-1) 格有障碍,直接输出 0表示没有方法可行
有障碍的格子,即A[i][j] = 1的格子 f[i][j] = 0即不能到达这一格子

边界情况:
当i-1 = 0 或 j-1 = 0时,即f[0][j] =1,f[i][0] =1; 表示边界点只有一种方式到达

输入A[i][j]是1,即格子有障碍的坐标位置,输出最终有几种方式到达右下角

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int m,n;
	cin>>m>>n;
	int A[m][n];
	memset(A,0,sizeof(A));
	int dp[m][n];
	int a,b;
	cin>>a>>b;
	while(a!=-1&&b!=-1)
	{
		A[a][b]=1;
		cin>>a>>b;
	} 
	
	if(A[0][0]==1||A[m-1][n-1]==1)  //开始、结束被堵住没有办法走出 
	{
		cout<<0<<endl;
		return 0;
	}
	dp[0][0]=1;                    //一定要设置初始值 
	for(int i=0;i<m;i++)
	{
		for(int j=0;j<n;j++)
		{
			if(A[i][j]==1) dp[i][j]=0;
			else
			{
				 if(i-1==-1||j-1==-1) dp[i][j]=1;
				else dp[i][j]=dp[i-1][j]+dp[i][j-1];
			}
		}
	}
	cout<<dp[m-1][n-1];
	return 0;
} 

 

二、秘密行动(蓝桥杯)

题目链接:http://lx.lanqiao.cn/problem.page?gpid=T2994
改题目也属于坐标型动态规划,像是斐波拉契数列变换而来的,

  1. 首先要读懂题目,写出dp[]数组(到底是一维数组还是多维数组),到达第i层的最小时间,就可以直接列dp[i]为到达第i层的最小时间
  2. 考虑动态转移方程,就是从哪儿来,到哪儿去(辅助思考,不过多考虑)
    从哪儿来:要么从第i-1层走到第i层,要么从第i-1层跳到第i层,要么从第i-2层跳到第i层,但是又要满足不能连续跳,既然不能连续跳,那在考虑走到第i层的时候我们就要判断上一次是咋弄得,上一次是跳还是走呢,很显然我们不知道,不知道什么就在dp状态里加入什么,加入跳或走对应0或1的状态
  3. 写出动态转移方程:dp[i][0]表示跳到第i层,dp[i][1]表示走到第i层。
    考虑跳到第i层时最少时间,那上一次只能是走的:dp[i][0]=min(dp[i-1][1],dp[i-2][1]);
    走到第i层上一次就随便:dp[i][1]=min(dp[i-1][0],dp[i-1][1])+high[i];
  4. 需要考虑清楚下标的问题,数组第一个数实际是第0层,走过第一个数才算到达第1层(走上第一层才算到达第一层)
    AC代码:
#include<iostream>
using namespace std;
#define MAXSIZE 10005
int high[MAXSIZE];
int dp[MAXSIZE][2];  //到达第i层所需要的最短时间,并且记录上一次动作(跳还是走) 
				   	//0表示跳,1表示走 
int n;
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++) 
	{
		cin>>high[i]; 
	} 
	dp[1][0]=0;dp[1][1]=high[1];
	for(int i=2;i<=n;i++)
	{
		//跳到i,走到i-2或走到i-3,取小的 
		dp[i][0]=min(dp[i-1][1],dp[i-2][1]);
		//走到i,从i-1过来(可走到i-1,也可以跳到i-1) , 
		dp[i][1]=min(dp[i-1][0],dp[i-1][1])+high[i];			
	}
	cout<<min(dp[n][0],dp[n][1]); 
	return 0;
}

在这里插入图片描述
0ms,真的是太快了,代码也短,就是有点考脑袋,dp yyds

序列型动态规划

题目描述:

有一排n栋房子,每栋房子要漆成3种颜色中的一种:红、蓝、绿
任何两栋相邻的房子不能漆成同样的颜色,第i栋房子染成红色、蓝色、绿色的花费分别是cost[i][0],cost[i][1],cost[i][2]
问最少需要花多少钱油漆这些房子

例子:
输入:n=3;cost=[ [14,2,11],[11][14][5], [14][3][10] ]

输出:10(第0栋房子蓝色,,第1栋房子绿色,第2栋房子蓝色,2+5+3=10)

解决方案:
1.前一步会对后一步造成影响,求最大最小值问题

2.设计状态:
最后一步:
最优策略中房子n-1一定染成了红、蓝、绿中的一种
n-1 : 红色---------n : 蓝色或绿色
n-1 :蓝色---------n : 红色或绿色
n-1 : 绿色---------n : 红色或蓝色

如果直接套用以前的思路,记录油漆前n栋房子的最小花费
根据套路,也需要记录油漆前n-1栋房子的最小花费
但是,前n-1栋房子的最小花费的最优策略中,不知道n-2是什么颜色,所以有可能和房子n-1撞色

在动态规划中不知道什么,就把它记录在状态中
求油漆前n栋房子并且n-1是红色、蓝色、绿色的最小花费
需要知道前n-1栋房子、并且n-2是红色、蓝色、绿色的最小花费为
f[i][0] ,f[i][1], f[i][2]

注意序列型动态规划记录的是前n个数据(不包含第n个数据)怎么怎么样,而坐标型记录的是第i个数据怎么怎么样_

3.状态转移方程:
f[i][0] = min{ f[i-1][1] +cost[i-1][0] , f[i-1][2] + cost [i-1] [0]}
f[i][1] = min{ f[i-1][0] +cost[i-1][1] , f[i-1][2] + cost [i-1] [1]}
f[i][2] = min{ f[i-1][0] +cost[i-1][2] , f[i-1][1] + cost [i-1] [2]}

4.初始条件:
前0栋房子,即不油漆任何房子的花费:f[0][0] = f[0][1] = f[0][2] =0

边界情况:
序列性动态规划就是处理0–n的数据,0处表示没有房子,直接等于0,如果是坐标型动态规划就是将第0个元素赋值为坐标(0,0)的值,不存在边界情况,序列型动态规划就是不需要考虑(0,0)处的数据,直接等于0
所以f数组需要开n+1的大小

#include<bits/stdc++.h>
using namespace std;
int Min(int a,int b,int c)
{
	if(a<b&&a<c) return a;
	if(b<a&&b<c) return b;
	if(c<a&&c<b) return c;
}
int main()
{
	int n;cin>>n;
	int arr[n][3];
	int a,b,c;
	for(int i=0;i<n;i++)
	{
		cin>>a>>b>>c;
		arr[i][0]=a;     //0--红色,1--蓝色,2--绿色 
		arr[i][1]=b;
		arr[i][2]=c;
	}
	int dp[n+1][3];   //油漆前n栋房子并且第n-1个房子是红色、蓝色、绿色的最小花费                                                                 
	dp[0][0]=0;  		//前0栋房子,表示没有房子,不花费钱 
	dp[0][1]=0;
	dp[0][2]=0;
	for(int i=1;i<=n;i++)
	{
		dp[i][0]=min(dp[i-1][1]+arr[i-1][0],dp[i-1][2]+arr[i-1][0]);  //前i个房子并且第i-1个房子是红色,则第i-2个房子只能是蓝色1,或者绿色2,再加上第i-1个红色房子的颜色的最小花费 
		dp[i][1]=min(dp[i-1][0]+arr[i-1][1],dp[i-1][2]+arr[i-1][1]);  //第i-1个房子是蓝色1,则第i-2个房子只能是 红色0或者绿色2 
		dp[i][2]=min(dp[i-1][0]+arr[i-1][2],dp[i-1][1]+arr[i-1][2]);	
	}
	for(int i=0;i<=n;i++)
	{
		for(int j=0;j<3;j++)
		cout<<dp[i][j]<<" ";
		cout<<endl;
	}
	cout<<Min(dp[n][0],dp[n][1],dp[n][2]);
/*	cost=[ [14,2,11],[11][14][5], [14][3][10] ]
	
		红      蓝      绿 
	0	0       0		 0
	1	14		2		11
	2	13/22	25/25	19/7
	*/
	return 0;
} 


总结:

1.序列型动态规划设置状态为…前i个…最小/方式数/可行性

2.在本问题中,发现需要知道i-1栋房子的最优策略中需要知道第i-2栋房子的颜色,这样才能确定第i-1栋房子染什么颜色
单纯用f[i-1] ,将无法区分,所以想到在状态中存进第i-2栋房子的颜色
发现缺什么条件就往状态中添加什么,序列加

划分型动态规划

题目描述:
有一段由A-Z组成的字母串信息被加密成数字串,有加密方式为:A-1,B-2,…Z-26
给定加密后的数字串S[0,…n-1] ,问有多少种方式解密成字符串
例:
输入 12
输出 2 (AB或L)
解决方案
1.前面一截的字符串会对后面字符串解码的取值有影响,计数问题
2.状态设计
对这一串数字进行划分,研究最后一种划分方式
最后一步:最后一个解码出来的字母可能对应的是最后一个数字,也可能对应的是最后两个数字
子问题:划分掉最后一个字母对应的数字,需要知道数字串前n-1和n-2个字符的解密方式数
所以设置状态数组dp[i] 为前i个字符解码方式数,(0,i-1] 个字符
3.状态转移方程
dp[i] =dp[ i - 1] +dp[ i - 2 ] ;
(+dp[ i - 2 ]但是要满足最后两位数字在0-26范围以内)
4.初始条件
dp[ 0 ] =1,即空串有1种方式解密—解密成空串
边界情况:
如果i=1,只看最后一个数字,即当i>=2时才看最后两个数字

#include<bits/stdc++.h>
using namespace std;
int main()
{
	string sa;cin>>sa;
	int len=sa.length();
	if(len=0) return 0;
	
	int dp[len+1];   //除了坐标型动态规划都这样令,每个数据表示前len个字符解码方式数 
	memset(dp,0,sizeof(dp));
	dp[0]=1;         //前0个字符,解码为空串,这样令省去很多麻烦 
	//i就是前i个字符,取sa(0,i-1] 
	for(int i=1;i<=len;i++)
	{
		if(sa[i-1]!='0') dp[i]+=dp[i-1];
		int t=(sa[i-1]-'0')*10+sa[i]-'0';
		if(i>=2) 
		{
			if(t>=10&&t<=26)
			{
				dp[i]+=dp[i-2]; 
			}
		}
		
	} 
	cout<<dp[len];    //输出也是固定的 
	return 0;
}  

总结:
划分型动态规划与序列型一样,设置的状态都是前i个怎么怎么样,只要不是坐标型都可以这样设置,数组大小也是固定的

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值