动态规划:线性dp、背包问题、区间1

本文介绍了动态规划和记忆化搜索的概念及其在多种问题中的应用,如斐波那契数列、走楼梯、最短路径等。通过实例展示了如何利用动态规划和记忆化搜索优化计算过程,降低复杂度,如解决棋盘问题、传球游戏、最长不下降子序列和最大子段和问题。动态规划的核心在于找出最优子结构并记录关键状态,而记忆化搜索则通过保存子问题的解来避免重复计算。
摘要由CSDN通过智能技术生成

记忆化搜索

将之后可以重复用到的子问题的答案进行记录。典例:斐波那契数列、走楼梯、走蜂窝。

斐波那契改进:走楼梯,第i阶台阶不能走。直接强行让第i级台阶方案数等于0即可。

推广:从起点到终点的最短路径方案数,可以通过广搜标记计算每一个点到达的方案数。

1163 -- The Triangle

图 1 显示了一个数字三角形。编写一个程序,计算从顶部开始到底部某处结束的路线上通过的最大数字总和。每一步都可以对角线向左或向右对角线向下。

从上往下写比较容易想,但从下往上写可以直接在顶点取到答案,数组开的也小,只用存输入数据即可,后面会自动更新。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f, maxn=105;
int n,f[maxn][maxn];
int main(){
	cin>>n;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=i;++j)
			scanf("%d",&f[i][j]);
	}
	for(int i=n-1;i>=1;--i){
		for(int j=1;j<=i;++j)
			f[i][j]+=max(f[i+1][j],f[i+1][j+1]);
	}
	cout<<f[1][1];
	return 0;
}

动态规划

分类加法原理:通过迭代相加得到答案

分步乘法原理:通过迭代相乘得到答案

多阶段决策过程最优化问题,一般阶段与阶段决策过程有相关性,通过记录每个阶段的最优决策结果来达到全局最优。

动态规划使用的基本条件:具有相同的子问题,满足最优子结构。比如说下列选择模4最小的路径的模数。只有找到最优子结构,才可以用动态规划。

 既然所有之前经历过的余数将会对后方产生结果,那不如直接设f[i][0,1,2,3],假设到达i点时需要其余数等于x,从前一个点到达i的路长为y,则之前的余数应当等于x-y。体现了动态规划的记录对未来有影响的数据的特点,即:

过去的状态只能通过现在的状态影响未来(翻译:要是你觉着这个状态数据会对未来产生影响,那你就把它记录下来)。走楼梯再次加难度:如果走过第50阶台阶,那就不能走第100阶台阶。

由“记录历史”原则,我们可以设置数组f[i][0,1],其中f[i][0]表示没上过50,f[i][1]表示上过。

i<50:	f[i][0]=f[i-1][0]+f[i-2][0], f[i][1]=0	//根本没上过50

i=50:	f[50][0]=0, f[50][1]=f[49][0]+f[48][0]	//记录50的方案数

50<i<100: f[i][0]=f[i-1][0]+f[i-2][0], f[i][1]=f[i-1][1]+f[i-2][1] //只要走过50级台阶,就从走过的方案中数

i=100:	f[i][0]=f[i-1][0]+f[i-2][0], f[i][1]=0	//上过50就别想上100

i>100:	f[i][0]=f[i-1][0]+f[i-2][0], f[i][1]=f[i-1][1]+f[i-2][1]	//同 50<i<100

动态规划的一般步骤

做多了就不用了,只用于初学阶段。

1、结合原问题和子问题确定状态,基本是题目求什么我们就设什么,并做一些细微调整(如走楼梯)。

 2、确定状态转移方程

  1. 参数够不够(记录历史状态)
  2. 分情况(最后一次操作方式,取不取,怎样取)
  3. 注意边界是什么
  4. 无后效性(如:求A就要求B,求B就要求C,求C就要求A,这种就不行)

3、考虑是否需要优化

4、编程实现:递归、记忆化搜索

例2 路径条数

N*M棋盘,过河卒需从左上角走到右下角,可以向下或向右,求路径条数。

左边界和上边界全部设为1,式子为f[i][j]=f[i-1][j]+f[i][j-1],递推即可。

又可以发现,这是个斜着的杨辉三角,即可以联系组合数求,等于C_{n+m} ^{n}

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网

[NOIP2002]过河卒


如图,A 点有一个过河卒,需要走到目标 B 点。卒行走规则:可以向下、或者向右。同时在棋盘上的任一点有一个对方的马(如上图的C点),该马所在的点和所有跳跃一步可达的点称为对方马的控制点。例如上图 C 点上的马可以控制 9 个点(图中的P1,P2 … P8 和 C)。卒不能通过对方马的控制点。

细节问题:要考虑到边界点只能从左向右或从上到下,所以一旦有马拦就全变为0。 

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map> 
#include<vector>
#include<set>
#include<cstring>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f, maxn=105;
int n,m,x,y,jg[30][30];
ll f[maxn][maxn];
int dx[]={0,2,2,1,1,-2,-2,-1,-1};
int dy[]={0,1,-1,2,-2,1,-1,2,-2};
int main(){
	cin>>n>>m>>x>>y;
	for(int i=0;i<9;++i)
		if(x+dx[i]<0||y+dy[i]<0||x+dx[i]>n||y+dy[i]>m) continue;
		else jg[x+dx[i]][y+dy[i]]=1;
	for(int i=0;i<=n;++i) f[i][0]=f[0][i]=1;
	for(int i=0;i<=n;++i){
		for(int j=0;j<=m;++j)
			if(!jg[i][j]){
				if(i==0&&j==0) continue;
				else if(i==0) f[i][j]=f[i][j-1];
				else if(j==0) f[i][j]=f[i-1][j];
				else f[i][j]=f[i-1][j]+f[i][j-1];
			}else f[i][j]=0;//
	}
	cout<<f[n][m];
	return 0;
}

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网

[NOIP2008]传球游戏


游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再次吹哨子时,传球停止,此时,拿着球没传出去的那个同学就是败者,要给大家表演一个节目。聪明的小蛮提出一个有趣的问题:有多少种不同的传球方法可以使得从小蛮手里开始传的球,传了m次以后,又回到小蛮手里。两种传球的方法被视作不同的方法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有3个同学1号、2号、3号,并假设小蛮为1号,球传了3次回到小蛮手里的方式有1->2->3->1和1->3->2->1,共2种。

实际上就是求第i次传球,球在第j个人手中的方案数。此题给我的感悟就是:要把数据转化成自己好理解的形式进行dp会好想一些;一般是从结果倒着推过程;数组就是为了存信息的,一维二维具体情况不重要。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f, maxn=35;
int n,m;
ll f[maxn][maxn];
int main(){
	cin>>n>>m;
	if(m==1){
		cout<<0;
		return 0;
	}
	f[0][1]=1;
	for(int i=1;i<=m;++i){
		for(int j=1;j<=n;++j){
			if(j==1) f[i][j]=f[i-1][j+1]+f[i-1][n];
			else if(j==n) f[i][j]=f[i-1][j-1]+f[i-1][1];
			else f[i][j]=f[i-1][j+1]+f[i-1][j-1];
		}
	}
	cout<<f[m][1];
	return 0;
}

最长不下降子序列

给出一组数,如果后面的数总大于前面的数,则称为最长不下降子序列。给出一组数,求最长不下降子序列(可以跳着选)。

我们并不知道当到第i个数的时候,是否该选i以及之前的长度能不能让我们去选i以成为当前最优解。所以我们考虑每次都必须选第i个数,并从第一个数开始找比它小的数,并将选择第i个数的最大长度等于之前小于i的数的最大的长度+1。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map> 
#include<vector>
#include<set>
#include<cstring>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f, maxn=35;
int n,m, a[maxn],f[maxn];
int main(){
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>a[i];
		f[i]=1;
	}
	for(int i=2;i<=n;++i){
		int maxx=0;
		for(int j=1;j<i;++j){
			if(a[i]>a[j]) maxx=max(maxx,f[j]);
		}
		f[i]=maxx+1;
	}
	int maxx=-1;
	for(int i=1;i<=n;++i)
		maxx=max(maxx,f[i]);
	for(int i=1;i<=n;++i)
		cout<<f[i]<<' ';
	cout<<endl;
	cout<<maxx;
	return 0;
}

[SHOI2002]滑雪 - 洛谷

输入的第一行为表示区域的二维数组的行数 RR 和列数 CC。下面是 RR 行,每行有 CC 个数,代表高度(两个数字之间用 11 个空格间隔)。输出区域中最长滑坡的长度。

为啥能保证查到数组中存放值,就一定是最长的坡呢?因为我们的代码逻辑十分严密,没算出来的值直接就派给分身(递归)去算了鸭!

细节问题:别忘了每个点最小长度都为1鸭,要不就会少算一个长度QAQ

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map> 
#include<vector>
#include<set>
#include<cstring>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f, maxn=105;
int r,c,mp[maxn][maxn],f[maxn][maxn],ans;
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
int calc(int x,int y){
	if(f[x][y]) return f[x][y];
	//因为代码逻辑严密,保证了只要是算出来的数,
	//就一定是x,y作为起点最长的滑雪道长度 
	f[x][y]=1;//别忘记设初始值QAQ 
	for(int i=0;i<4;++i){
		int xx=x+dx[i], yy=y+dy[i];
		if(xx<1||xx>r||yy<1||yy>c||mp[xx][yy]>=mp[x][y])
			continue;
		calc(xx,yy);
		f[x][y]=max(f[x][y],f[xx][yy]+1);
	}
	return f[x][y];
}
int main(){
	cin>>r>>c;//行列 
	for(int i=1;i<=r;++i)
		for(int j=1;j<=c;++j)
			cin>>mp[i][j];//读入图 
	for(int i=1;i<=r;++i)
		for(int j=1;j<=c;++j)
			ans=max(ans,calc(i,j));
	cout<<ans;
	return 0;
}

最大子段和 - 洛谷

给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大。

如果我们考虑a[i]为第i个数,f[i]表示第i个数一定要选的区间和,那么可以从一开始维护一个最大数的区间段,即f[i]=max(a[i], f[i-1] + a[i]) ,因为已经保证了之前选的区间一定是最大的且与i连续的,所以递推式子成立。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f, maxn=200005;
int n,a[maxn],f[maxn],ans=-inf;
int main(){
	cin>>n;
	for(int i=1;i<=n;++i)
		cin>>a[i];
	for(int i=1;i<=n;++i)
		f[i]=max(f[i-1]+a[i],a[i]);
	for(int i=1;i<=n;++i)
		ans=max(ans,f[i]);
	cout<<ans;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值