挑战30天C艹基本入门(DAY5--动态规划)

#今天的动态规划可是c语言里面的重中之重,也是我们学习的路上迈不开的一个问题。

当时高中的时候就学的不明不白地,今天复习一波,才感觉终于守得云开见月明,豁然开朗了,因此写下本篇,同时分享一下我自己的理解,希望帮助到更多迷惑中的人。

动态规划,可以帮我们解决好多实际问题。

动态规划的意思和他字面意思差不多:在一个动态的过程中,不断更新我们的最优解,得到全局的最优解。听上去和贪心差不多,(可以参考我上一篇文章)但是贪心主要是局部最优解,而非一个动态的过程。因此许多能用贪心解决的问题,我们也可以用动态规划来解决。

可见动态规划的适用性广泛以及重要性强。

那我们接下来就进入动态规划的学习中来。

动态规划

我们动态规划一般分为以下几步:

1.我们要清楚状态的描述和定义:我们一般用dp[i][j]来定义状态,从1,1到i,j经过的得到的最大值。

2.状态转移方程(这一步是动态规划的核心):dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j];

3.确定边界:如果从上往下来的话,那么我们的dp[i][j]=a[1][1],即确定了上边界。

基本就分为以下三步,我们熟练掌握,最重要的就是我们的状态转移方程,那么接下来我们来看一道动态规划的题目:

数字金字塔,要求遍历得到最大值。

我们按照我们前面的三个思路:

第一步我们首先定义二维数组a和dp。先将题目中的表导入a中。

第二步就是我们的状态转移方程:我们可以知道,下面最大值得去上面两个分支的最大值,再加上下一行的数值。因而我们得到状态转移方程:dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j];

第三步我们确定边界:dp[i][j]=a[1][1],即确定了上边界。

我们需要知道我们最后得到的dp[i][j],只是最终的一个值,因此我们要与最大值做对比,在每一步循环中对比,然后取最大值。

那么我们就可以得到我们下面的代码:

#include<bits/stdc++.h>
using namespace std;
int a[1001][1001],dp[1001][1001];
int main()
{
	int n;
	
	int maxx=0;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		{
			cin>>a[i][j];
		}
	}
	dp[1][1]=a[1][1];
	for(int i=2;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j];
			maxx=max(maxx,dp[i][j]);
		}
	}
	cout<<maxx<<endl;
	return 0;
}

这道题也算动态规划比较经典的题目啦,学会了这个,我们动态规划就基本入了门啦!(加油加油!!!)

那我们下面看看这样一道题,小试牛刀一波:

求不下降子数列的最大值,并要求输出这个集合。

这道题难度就增加了一点,题目类似于我们昨天写的那个贪心算法的导弹拦截哪一道题目。

我们用动态规划的思路三步走一下:
第一步我们要先确定变化的集合元素。a[]dp[]

第二部我们要写出状态转移方程:  if(a[i]>=a[j])  dp[i]=max(dp[i],dp[j]+1);如果后面的数比前面的数大,那么我们就判断是不动,还是要走前驱+1,我们取最大值就好啦。

第三步我们要确定边界:我们默认把dp[201]=1;

这样我们很容易就求出来了max的值。

#include<bits/stdc++.h>
using namespace std;
int n,a[201],dp[201];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		dp[i]=1;
	}int maxx=0;
	for(int i=2;i<=n;i++)
	{
		for(int j=1;j<i;j++)
		{
			if(a[i]>=a[j])
			{
				dp[i]=max(dp[i],dp[j]+1);
				maxx=max(maxx,dp[i]);
			}
		}
	}
	cout<<"max="<<maxx<<endl;
	return 0;
}

问题来了,我们该怎么输出集合呢??

这一点确实需要我们的大量思考:

我们现在面临的问题就是不知道dp是保留自身,还是走了dp[j]+1这一步,如果我们找到了所有走dp[j]的集合,那么我们相当于找到了前驱元素,然后我们倒叙输出即可。

该怎么做呢?

没错,我们添加if判断条件即可,用if判断条件来取代max函数

这样我们就可以知道什么时候走的是前驱数列。

代码部分如下啦:

#include<bits/stdc++.h>
using namespace std;
int n,a[201],dp[201],pre[201];
void print(int dex)
{
	if(dex==0)return;
	print(pre[dex]);
	cout<<a[dex]<<" ";
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		dp[i]=1;
	}int maxx=0;
	int preindex=1;
	for(int i=2;i<=n;i++)
	{
		for(int j=1;j<i;j++)
		{
			if(a[i]>=a[j])
			{
				//dp[i]=max(dp[i],dp[j]+1);
				if(dp[j]+1>dp[i])
				{
					dp[i]=dp[j]+1;
					pre[i]=j;
					
				}
				if(dp[i]>maxx)
					{
						maxx=dp[i];
						preindex=i;
					}
					
			}
		}
	}
	cout<<"max="<<maxx<<endl;
	print(preindex);
	/*while(preindex)
	{
		cout<<a[preindex]<<" ";
		preindex=pre[preindex];
	}*/
	return 0;
}

有了以上的基础,那我们看看我们noi的一道题目吧,也就是昨天我们写过的导弹拦截问题:

看看如何用动态规划来进行求解:

 

我们可以发现,这道题的本质就是求最长不上升子数列,和最长下降子数列。

最长不上升子数列就是我们的导弹数目。

最长下降子数列就是我们的拦截系统数目。

由上面的基础,我们轻松写出代码。

#include<bits/stdc++.h>
using namespace std;
int a[2000];
int dp[2000],dpp[2000];

int main()
{
	int n=1;
	int maxx=0;
	int xitong=0;
	while(cin>>a[n])
	{
		dp[n]=1;
		dpp[n]=1;
		n++;
	}
	for(int i=2;i<=n-1;i++)
		for(int j=1;j<i;j++)
		{
			if(a[i]<=a[j])
			dp[i]=max(dp[i],dp[j]+1);
			maxx=max(maxx,dp[i]);
			if(a[i]>a[j])
			dpp[i]=max(dpp[i],dpp[j]+1);
			xitong=max(xitong,dpp[i]);
			
		}
	cout<<maxx<<endl;
	cout<<xitong;
	
	return 0;
}

 

 以上就是我们动态规划的模板类题目啦,因为内容过多需要大家下去消化吸收,一定要坚持哦,每天抽出来半个小时,一个月下来整个人就焕然一新啦。

然后之后我会出一个关于背包问题的专题,作为动态规划的拓展类知识,放在一个文章里内容过多,也不好学习,所以分为两个章节。

谢谢大家支持啦!!!(哇塞,俺现在已经100+粉丝了,小激动,感觉我就是世界首富了,哈哈哈(●ˇ∀ˇ●))

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值