DP自我见解

可以这样形象地认为DP:

动态规划实际上就像一个多米诺骨牌一样。从第一张牌开始往后翻滚,每一张牌实际上都是一种状态。我们给定了一个初始条件,就是给了第一张牌一个动力。将牌与牌之间的位置距离摆好,就是确定状态与状态之间的转移方程,将所要要求的骨牌推到,就得到了我们想要的结果。

需要强调的是,和骨牌有些区别的是,实际中的转移不应当是看成一个牌对另一个牌的作用,应当看成是该牌前面所有的牌对另一个牌的作用。
 

1、划分:按照问题的特征,把问题分为若干阶段。注意:划分后的阶段一定是有序的或者可排序的

2、确定状态和状态变量:将问题发展到各个阶段时所处的各种不同的客观情况表现出来。状态的选择要满足无后续性

3、确定决策并写出状态转移方程:状态转移就是根据上一阶段的决策和状态来导出本阶段的状态。根据相邻两个阶段状态之间的联系来确定决策方法和状态转移方程

4、边界条件:状态转移方程是一个递推式,因此需要找到递推终止的条件

经典的数塔问题也是dp算法的入门问题之一

假设你有这么一个数塔,你的目标是求最底层到最高层,求出最大路径和

 

 

比如3->7->2->9这个路径,他的路径和是3+7+2+9

不难发现如果要求到9的最大路径和,首先要求出他前一层的最大路径

核心代码dp[i][j]=max(dp[i-1][j],dp[i-1][j+1])+a[i][j]

dp[i][j](9的最大路径和)

a[i][j](9自己)

dp[i-1][j](前一层5的最大路径和)dp[i-1][j+1](前一层2的最大路径和)

在前一层的最大路径和取大的那一个

例题洛谷-->采药

定义dp[i][j]为采前i朵,消耗时间j内的最大价值
    
不难得出以下两种情况
1.当前时间可以采走这支草药
  (1)如果要采这支采药,那先前状态就要预留j-t[i]时间来满足定义
  (2)如果不采那最大价值就是先前状态dp[i-1][j]
  因为不清楚哪一种情况更好,但是我们只需要最大值,所以max
  综上dp[i][j]=max(dp[i-1][j],dp[i-1][j-t[i]]+v[i]);
 
2.当前时间不够采走这支草药,采不了那就不采,继承先前状态.  
  即dp[i][j]=d[i-1][j]
 
或者说延续用上文的想法,n支草药价值最大我不知道
如果只有1支草药呢?,那我是不是就知道了
1支草药知道了,那我2支草药是不是也知道了

直接从基础状态循环到结束,即从第一支草药开始循环。

显而易见不能从采最后几朵开始往前判断,前面状态都不清楚,怎么可以从后面开始。

#include <bits/stdc++.h>
using namespace std;
 
int T,m,t[101],v[101],dp[101][1001];
int main(){
    cin>>T>>m;
    for(int i=1;i<=m;++i)
        cin>>t[i]>>v[i];
    for(int i=1;i<=m;++i){
        for(int j=1;j<=T;++j){
            if(j>=t[i])dp[i][j]=max(dp[i-1][j],dp[i-1][j-t[i]]+v[i]);
            else dp[i][j]=dp[i-1][j];
        }
    }
    cout<<dp[m][T];
    return 0;
}

例题:疯狂的采药 - 洛谷

和上题采药的区别就是,每个药可以无限采

定义dp[i][j]为采前i朵(无限采),消耗时间j内的最大价值
    
不难得出以下两种情况
1.当前时间可以采走这支草药
  (1)继承先前状态
  (2)迭代自己
  综上dp[i][j]=max(dp[i-1][j],dp[i][j-t[i]]+v[i]);
                                注意此处是i,不是i-1
 
2.当前时间不够采走这支草药,采不了那就不采,继承先前状态.  
  即dp[i][j]=d[i-1][j]
#include <bits/stdc++.h>
using namespace std;
const int MAXM=1e4+1;
const int MAXT=1e7+1;
 
int T,m,t[MAXM],v[MAXM];
long long dp[MAXT];
 
int main(){
    cin>>T>>m;
    for(int i=1;i<=m;++i){
        cin>>t[i]>>v[i];
    }
    for(int i=1;i<=m;++i){
        for(int j=t[i];j<=T;++j){
            dp[j]= max(dp[j],dp[j-t[i]]+v[i]);
        }
    }
    cout<<dp[T];
    return 0;
}

例题[NOIP2012 普及组] 摆花 - 洛谷

可以发现摆花方案只和下面几种属性有关:
1. 花的种类

2. 花的数量,以及总数要小于一个限定数 -> 花的总数

3. 花的顺序

尝试做出定义f[i]为[j]用上前  种花,且到当前为止已经用了  盆花的所有方案数。

当我们正序迭代这个式子的时候,可以发现 3. 花的顺序 这个属性被隐含解决了。

即这个定义目前看来还是可行的。

根据定义易得:f[i][j]=\sum_{k=j-a[i]}^j{f[i-1][k]},其中  1<=j<=m(不管他到现在最多能用多少,直接暴力枚举)。

初始化则为一种花都不用,一盆花都没有,即f[i][0]=1  。其中 0<=i<=n。

出现了 k=j-a[i]所以循环的时候要注意下标越界,判断k>=0  。

 

#include <bits/stdc++.h>
using namespace std;
const int MOD=1e6+7,N=101;
 
int n,m,f[N][N],a[N];
 
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;++i)
        cin>>a[i];
 
    for(int i=0;i<=n;++i)
        f[i][0]=1;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            for(int k=j-a[i];k<=j;++k)
                if(k>=0)f[i][j]=(f[i][j]+f[i-1][k])%MOD;
    cout<<f[n][m];
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值