#今天的动态规划可是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+粉丝了,小激动,感觉我就是世界首富了,哈哈哈(●ˇ∀ˇ●))