可以这样形象地认为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;
}
可以发现摆花方案只和下面几种属性有关:
1. 花的种类
2. 花的数量,以及总数要小于一个限定数 -> 花的总数
3. 花的顺序
尝试做出定义f[i]为[j]用上前 种花,且到当前为止已经用了 盆花的所有方案数。
当我们正序迭代这个式子的时候,可以发现 3. 花的顺序 这个属性被隐含解决了。
即这个定义目前看来还是可行的。
根据定义易得:,其中 (不管他到现在最多能用多少,直接暴力枚举)。
初始化则为一种花都不用,一盆花都没有,即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;
}