动态规划入门

怎么说最近刷到了一个up主,然后他讲题目的方式蛮搞笑的,我就被他给整笑了哈哈哈哈,然后看了一下他的其他视频,然后就来记录一下好了!
(1)
(2)
(3)

一、状态转移(递推)

什么是动态规划?什么是状态转移?什么是空间换时间?

基础入门第一题:斐波那契数列

在这里插入图片描述
这里的n不能很大,不然会超过int整数型的最大范围。
这是基础的递推公式。
f [ i ] = f [ i − 1 ] + f [ i − 2 ] f[i]=f[i-1]+f[i-2] f[i]=f[i1]+f[i2]
初始化第一项和第二项,然后开始用公式来递推。

递推其实是最简单的状态转移

然后再是斐波那契数列的简单应用—爬楼梯

在这里插入图片描述
然后这里的递推公式也是
f [ i ] = f [ i − 1 ] + f [ i − 2 ] f[i]=f[i-1]+f[i-2] f[i]=f[i1]+f[i2],也可以叫他状态转移方程。

在这里插入图片描述

使用最小花费爬楼梯

在这里插入图片描述
在这里插入图片描述
这道题目和爬楼梯那道题很相似,我们只不过是从计算方案数转化为了计算最小值。
f [ i ] f[i] f[i]表示爬到第i层的最小花费数。

int n;
int f[1001]={0,0};
for(int i=2;i<n;i++)
f[i]=min(f[i-1]+cost[i-1],f[i-2]+cost[i-2]);

虽然学到这里,并没有什么卵用,但是我们可以从以上的题目总结出我们刷动态规划的大致流程。

1、设计状态

2、写出状态转移方程

3、设定初始状态

4、执行状态转移

5、返回最终的解

接下来我们做一下进阶题,找找感觉:最大子数组和

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>

using namespace std;

int main()
{
    int dp[100001];
    int maxv=nums[0];
    dp[0]=nums[0];
    for(int i=1;i<n;i++)
    {
        dp[i]=max(dp[i-1]+nums[i],nums[i]);
        maxv=max(maxv.dp[i]);
    }
    printf("%d",maxv);   
    
    return 0;
}

二、映射(线性dp)

什么是动态规划?什么是线性dp?什么是O(1)?
来吧,来一个中等题压压惊

打家劫舍

在这里插入图片描述

规则:如果第i个数取,那么第i+1个或者是第i-1个不能取。
在这里插入图片描述

d p [ i ] dp[i] dp[i]表示前i个整数通过某种选取方案能够获得的最大值。
初始状态: d p [ 0 ] = n u m s [ 0 ] dp[0]=nums[0] dp[0]=nums[0] d p [ 1 ] = m a x ( d p [ 0 ] , d p [ 1 ] ) dp[1]=max(dp[0],dp[1]) dp[1]=max(dp[0],dp[1])
咱们的转移方程为
d p [ i ] = m a x ( d p [ i − 1 ] , d p [ i − 2 ] + n u m s [ i ] ) dp[i]=max(dp[i-1],dp[i-2]+nums[i]) dp[i]=max(dp[i1],dp[i2]+nums[i])
第i个数不取 第i个数取

#include<bits/stdc++.h>

using namespace std;

int main()
{
    int dp[110];
   dp[0]=nums[0];
   for(int i=1;i<n;i++)
   {
       if(i==1)//为了防止数组越界
        dp[1]=max(nums[0],nums[1]);
       else dp[i]=max(dp[i-1],dp[i-2]+nums[i]);
   }
   cout<<dp[n-1]<<endl;
    return 0;
}

线性dp:是因为状态数与时间复杂度呈线性关系O(n)

状态转移复杂度O(1)–时间复杂度是与n无关的

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由于本题的数字不大,n<=10000,所以我们要合理安排。
所以我们可以把数字映射到数组里。有点***打家劫舍***那道题的味道。

#include<bits/stdc++.h>

using namespace std;
int sum[10010],val[10010],nums[10010];
int f(int* nums,int n)//“打家劫舍”的代码
{
    int i;
    int dp[10010];
    dp[0]=nums[0];
    for(int i=1;i<n;i++)
    {
        if(i==1)
            dp[1]=max(nums[0],nums[1]);
        else dp[i]=max(dp[i-1],dp[i-2]+nums[i]);
    }
    return dp[n-1];
}


int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>nums[i];
   memset(sum,0,sizeof sum);
   for(int i=0;i<n;i++)
    sum[nums[i]]++;
   for(int i=0;i<=1000;i++)
    val[i]=i*sum[i];//映射,表示选取i这个数以后能够获得的点数
   cout<<f(val,10001);
    return 0;
}

三、前缀最值

买卖股票的最佳时期

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们用dp[i]表示前i个元素的最小值
状态转移方程
d p [ i ] = m i n ( d p [ i − 1 ] , n u m s [ i ] ) dp[i]=min(dp[i-1],nums[i]) dp[i]=min(dp[i1],nums[i])
初始状态
d p [ 0 ] = n u m s [ 0 ] dp[0]=nums[0] dp[0]=nums[0]

#include<bits/stdc++.h>

using namespace std;
const int N=10010;
int preva[N],a[N];

int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>a[i];
    int ans=0;
    for(int i=0;i<n;i++)
    {
        if(i==0)
            preva[0]=a[0];
        else preva[i]=min(preva[i-1],a[i]);
    }
    for(int i=1;i<n;i++)
        ans=max(ans,a[i]-preva[i]);
    cout<<ans;
    return 0;
}

后缀最值

最大元素替换

在这里插入图片描述
我们用postmax[i]表示从i~n-1里面的最大元素
状态转移方程
p o s t m a x [ i ] = m a x ( p o s t m a x [ i + 1 ] , n u m s [ i ] ) postmax[i]=max(postmax[i+1],nums[i]) postmax[i]=max(postmax[i+1],nums[i])
初始状态
p o s t m a x [ n − 1 ] = n u m s [ n − 1 ] postmax[n-1]=nums[n-1] postmax[n1]=nums[n1]

#include<bits/stdc++.h>

using namespace std;
const int N=10010;
int postmax[N],a[N];

int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>a[i];
    int ans=0;
    for(int i=n-1;i>=0;i--)
    {
        if(i==n-1)
            postmax[i]=a[i];
        else postmax[i]=max(postmax[i+1],a[i]);
    }
    for(int i=0;i<n;i++)
      if(i==n-1)
      a[i]=-1;
    else a[i]=postmax[i+1];
    for(int i=0;i<n;i++)
        cout<<a[i]<<" ";
    return 0;
}

最佳观光组合

在这里插入图片描述
在这里插入图片描述
首先我们根据题意可知
一对景点的评分: n u m s [ i ] + n u m s [ j ] + i − j nums[i]+nums[j]+i-j nums[i]+nums[j]+ij
然后我们在把他划分一下, ( n u m s [ i ] + i ) + ( n u m s [ j ] − j ) (nums[i]+i)+(nums[j]-j) (nums[i]+i)+(nums[j]j)
我们会发现前面的是定值,后面的是求前缀最大值。他们两个相互之间独立。

#include<bits/stdc++.h>

using namespace std;
const int N=10010;
int premax[N],a[N],nums[N];

int main()
{
    int n;
    cin>>n;
    for(int i=0; i<n; i++)
    {
        cin>>nums[i];
        a[i]=nums[i]+i;
    }
    int ans=0;
    for(int i=0; i<n; i++)
    {
        if(i==0)
            premax[i]=a[i];
        else premax[i]=max(premax[i-1],a[i]);
    }
    for(int j=1; j<n; j++)
        ans=max(ans,premax[j-1]+nums[j]-j);
    cout<<ans<<endl;
    return 0;
}

接雨水

在这里插入图片描述
他其实也就是前缀最值加上后缀最值
题目分析:min(左边最高,右边最高)-自己高度

#include<bits/stdc++.h>

using namespace std;
const int N=10010;
int premax[N],a[N],postmax[N];

int main()
{
    int n;
    cin>>n;
    for(int i=0; i<n; i++)
    {
        cin>>a[i];
    }
    int ans=0;
    for(int i=0; i<n; i++)
    {
        if(i==0)
            premax[i]=a[i];
        else premax[i]=max(premax[i-1],a[i]);
    }
    for(int i=n-1;i>=0;i--)
    {
        if(i==n-1)
            postmax[i]=a[i];
        else postmax[i]=max(postmax[i+1],a[i]);
    }
    for(int i=1; i<n; i++)
        ans+=min(premax[i],postmax[i])-a[i];
    cout<<ans<<endl;
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值