2021-07-16 JZYZ暑期集训Day12 DP阶段性总结

本文介绍了动态规划的概念、使用前提和步骤,并通过几个实例,如IOI1994数字三角形、黑熊过河、Atcoder_DP中的青蛙问题和导弹拦截问题,详细讲解了动态规划的解题思路和应用。动态规划的核心在于确定状态、边界条件和状态转移方程,通过记忆化搜索或直接动态规划求解最优化问题。
摘要由CSDN通过智能技术生成

DP

动态规划(DP)是对一类最优化问题的解法。动态规划问题每次决策时依赖于当前状态,又随即引起状态的转移,一个决策序列就是在变化的状态中产生出来的,这种多阶段最优化决策解决问题的过程称为动态规划。
动态规划的使用前提:
(1)最优化原理。(搬CCF的) 在作出一个决策之后,剩下的问题是与原问题性质相同的子问题,只要规模或参数发生变化,要使原问题最优,子问题必须最优。
(2)无后效性:这个非常好理解,其实就是把一个问题分成两个问题子问题和原问题,我们在子问题里面求最优值,跟我原问题有什么关系呢?(抓鲁迅跟我周树人有什么关系? 揍张大威跟我张大帅有什么关系?
子问题的决策序列不会影响原问题的决策。但是你不能从后面推到前面,比如斐波那契数列,怎么可能从fibo[3]推出fibo[1]呢?这是不科学的!

动态规划的步骤:
(1)确定状态(搬CCF的) 状态是一种数学形式,能刻画一个最优解的解的结构特征,能够把题目里的必不可少的要素包含进来。
状态到底是什么呢?我们通过下面的例题看吧。。↓
(2)确定边界条件:跟递归一样,我们写DP的时候也要确定状态的最优解。如果不写边界条件那写DP干什么。。和DFS一样,有些情况也要写边界条件。
(3)使用各种骚操作优化,比如记忆化搜索。

[IOI1994]数字三角形 Number Triangles

观察下面的数字金字塔。

写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。


      7 
    3   8 
    8   1   0 
  2   7   4   4 
  4   5   2   6   5

在上面的样例中,从7 到 3 到 8 到 7 到 5 的路径产生了最大
输入格式
第一个行包含 R(1<= R<=1000) ,表示行的数目。
后面每行为这个数字金字塔特定行包含的整数。
所有的被供应的整数是非负的且不大于100。
输出格式
单独的一行,包含那个可能得到的最大的和。
样例数据
input
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
output
30

分析:
这题有Ao多的做法。但是我们第一个想到的估计是贪心,这题能不能用贪心做呢?答案很明显,不可以。为什么?因为贪心在每行找一个最大值并不一定是真的最优解。至于为什么,自己走一遍就知道了。
(1)用DFS ,问题要求的是从最高点走到最低点,走法规则很明确,起点终点固定,可以考虑用搜索解决

#include<bits/stdc++.h>
using namespace std;
int a[1000][1000],f[1000][1000],n,ans;
void dfs(long long x,long long y,long long curr){
   
	if(x==n){
      //边界条件
		if(curr>ans) ans=curr;
		return;
	}
	dfs(x+1,y,curr+a[x+1][y]);     //向左走
	dfs(x+1,y+1,curr+a[x+1][y+1]); //向右走
}
int main(){
   
	cin>>n;
	for(int i=1;i<=n;i++)
	   for(int j=1;j<=i;j++)
			cin>>a[i][j];
	ans=0;
	dfs(1,1,a[1][1]);
	cout<<ans<<endl;
	return 0;
	   
}

这种垃圾做法的复杂度是O(2^(n-1)),用军哥的话来说,“很拉胯”。
N稍稍一大,瞬间爆炸。

(2)可以用记忆化搜索给他调到O(N²)。之所以会超时是因为重复搜索,比如从(1,1)走到(3,2)有“左右”和“右左”两种路径,被搜索了两次。我们完全可以在第一次搜索(3,2)的时候就记录下(3,2)到终点的最大权值和。下次再来到(3,2)的时候就跳过了。
定义dfs(x,y)表示从(x,y)出发到终点的最大权值和,答案就是dfs(1,1)。
然后我们定义第一步向左和第一步向右的操作。
第一步向左:就先从(x,y)出发到终点的这类路径就被分成两个部分,先从(x,y)到(x+1,y),再从(x+1,y)到终点,这一部分权值固定为a[x][y],要使得这种情况路径权值和最大,那么第二部分从(x+1,y)到终点的权值和也要最大,这一部分与前面的dfs(x,y)的定义类似,仅仅是参数不同,
综上所述,第一步向左的路径的最大权值和为a[x][y]+dfs(x+1,y)。
那么第一步向右:也一样,a[x][y]+dfs(x+1,y+1)。
为了避免重复搜索,我们开f[x][y]记录从(x,y)出发到终点路径的最大权值和,一开始全部初始化为-1表明没有被计算过。在计算dfs(x,y)时,首先查询f[x][y],如果不等于-1,那么就被计算过,直接返回f[x][y]即可。否则计算出dfs(x,y)的值并存在f[x][y]中。
完全能过。

#include<bits/stdc++.h>
using namespace std;
const int maxn=505;
int a[maxn][maxn],f[maxn][maxn],n;
int dfs(int x,int y){
   
	if(f[x][y]==-1){
   
		if(x==n) f[x][y]=a[x][y];
		else f[x][y]=a[x][y]+max(dfs(x+1,y),dfs(x+1,y+1));
	}
	return f[x][y];
}
int main(){
   
	cin>>n;
	for(int i=1;i<=n;i++){
   
		for(int j=1;j<=i;j++){
   
			cin>>a[i][j];
		}
	}
	for(int i=1;i<=n;i++){
   
		for(int j=1;j<=i;j++){
   
			f[i][j]=-1;
		}
	}
	dfs(1,1);
	cout<<f[1][1]<<endl; 
	
	return 0;
}

(3)动态规划
诶?我们今天学的是动态规划吗?
DFS完全能A了啊!
所以我懒得写了。
好吧,今天学的是动态规划,还是用动态规划做一波吧。
其实记忆化搜索本质上已经是动态规划了。
我们用普通的动态规划写一遍。
1.确定状态:题目要求从(1,1)出发到最底层路径最大权值和,路径是由各个点串联而成,起点固定,终点和中间点相对不固定,因此定义f[x][y]表示从(1,1)出发到(x,y)的路径最大权值和。
则ans=max(f[n][1],f[n][2]…f[n][n])
2.确定状态转移方程和边界条件:不去考虑(1,1)到(x,y)的每一步是如何走的,只考虑最后一步是怎么走的,根据最后一步是向左和向右分成如下情况:

(1)向左:最后一步是从(x-1,y)走到(x,y),此类路径被分割成两部分,第一部分是计算从(1,1)走到(x-1,y),第二部分是从(x-1,y),要计算此类路径的最大权值和,就必须用到第一部分的最大权值和,就是f[x-1][y]+a[x][y]。
(2)向右,同上,f[x-1][y-1]+a[x][y]。
总结,状态转移方程就是
f [ x ] [ y ] = m a x ( f [ x − 1 ] [ y − 1 ] , f [ x − 1 ] [ y ] ) + a [ x ] [ y ] f[x][y]=max(f[x-1][y-1],f[x-1][y])+a[x][y] f[x][y]=max(f[x

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值