1.关于线性dp:
线性dp就是在数组里进行dp,即dp[i]可以由dp[i-1],dp[i-2]....等等转移过来,当然也可以从dp[i+1],dp[i+2]那边转移过来
关于最优子结构的理解:
因为动态规划的性质,即满足最优子结构,大部分位置的转移方式都相同,因此它可以由状态转移方程来递推,至于边界部分初始化就行了(所谓的动态规划的初始化就是小部分位置因为位置的特殊性不和其他地方的转移方式相同,所以需要手动初始化)
而有些题目由于自身的特殊性,有些地方转移方式和一般的地方不一样,因此有的位置需要分类讨论
这里以种树IV为例:
设计状态dp[i][j]为:种到 i 位置,已经种了 j 棵树的最大收益
它的一般的转移方程非常的直观,它由 i-1 位置和 i-2 位置转移过来:
for(int j=1;j<=m;j++) dp[i][j]=max(dp[i-1][j],dp[i-2][j-1]+a[i]);
但是位置1和位置2的转移就不能按这个式子来了,因为式子中有 i-2 ,代进去是负数
这两个位置就是特殊的位置,需要特殊转移处理,这里的转移就是初始化:
dp[i][1]=a[i];
dp[i][1]=max(dp[i-1][1],a[i]);
这样就初始化好了
而题目中还有个特殊位置需要特殊处理:k位置
在第k位置只能由前一个位置转移,因为第k的位置无法种树:
for(int j=1;j<=m;j++) dp[i][j]=dp[i-1][j];
至此,所有的特殊位置都处理好了:
Code:
#include <bits/stdc++.h>
using namespace std;
const int mxn=1e6+10,inf=0xc0c0c0c0;
int n,m,k;
int a[mxn],dp[mxn][11];
int main(){
scanf("%d%d%d",&n,&m,&k);
memset(dp,inf,sizeof(dp));
dp[0][0]=0;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
dp[i][0]=0;
if(i==k){
for(int j=1;j<=m;j++) dp[i][j]=dp[i-1][j];
}else if(i==1) dp[i][1]=a[i];
else if(i==2) dp[i][1]=max(dp[i-1][1],a[i]);
else{
for(int j=1;j<=m;j++) dp[i][j]=max(dp[i-1][j],dp[i-2][j-1]+a[i]);
}
}
int ans=dp[n][m];
//for(int j=0;j<=m;j++) ans=max(ans,dp[n][j]);
printf("%d\n",ans);
}
对于状态设计:
考虑其决策和进行决策之后对情况的分类讨论
对于转移方向:
在考虑转移的时候,一般去考虑决策,执行一个决策之后对其情况进行分类讨论
如果转移的方程中 i 位置的状态是由同一位置的状态的其他属性(也可以叫参数?反正就是j,k等其他属性)转移,那么这个属性变量j,k等的转移方向就需要确定
比如完全背包:
#include <bits/stdc++.h>
using namespace std;
const int mxn=1e3+10;
int n,m;
int v[mxn],w[mxn],dp[mxn][mxn];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d%d",&v[i],&w[i]);
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
if(v[i]>j) dp[i][j]=dp[i-1][j];
else dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i]);
}
}
printf("%d\n",dp[n][m]);
return 0;
}
它的含义就是:
如果当前背包容量装不下物品,那么就由dp[i-1][j]转移
否则就有两种决策:i 位置选还是不选
如果 i 位置选了,那么就从前 i 个物品,背包容量为j-v[i]的状态转移
如果 i 位置没选,那么就从dp[i-1][j]转移
这里的
for(int j=0;j<=m;j++)
枚举方向是确定的,因为dp[i][j]必须由dp[i][j-v[i]]转移,是从小到大转移,因此这里必须是从小到大枚举
而01背包就不需要定向枚举,因为01背包的状态只由 i-1 位置转移:
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) {
if(j>=v[i]) dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
else dp[i][j]=dp[i-1][j];
}
}
这里的 j 可以正序枚举,也可以倒序枚举,甚至可以乱序枚举
个人认为01背包可以看成是一种特殊的树上背包,只需把树看成一条长链即可(个人理解不一定对)
关于初始化:
dp数组含义是求最小值的:初始化成无穷大
dp数组含义是求最大值的:初始化成无穷小
无穷大或小其实是让一些不合法的状态不可能取到
其他的按位置的特殊转移方式去处理
注意初始化要紧扣状态的定义
后面如果要判断一个状态是否合法,不要去判断dp值是否等于inf还是mnf,因为无穷值可能会加上或减去一些偏移量,就不一定等于inf或mnf了,但是还是不可能取得到,因为太大或小了
答案也未必就是最后那一个状态,可能还要枚举一下取最值,这个根据dp数组具体定义来看
比如01背包中,dp[i][j]可以表示成:装到位置i,背包容量不超过j的最大收益,此时答案就是dp[n][m],但是如果表示成装到位置i,背包容量恰好为j的最大收益,那就要枚举一遍取最大了,因为它不一定能装满,可能是不合法的情况,这种情况当然也是要把数组初始化成无穷小的
关于背包dp:
待补:分组背包,多重背包,二维费用背包
关于树形dp:
树上背包:
二叉苹果树(树上01背包):
(149条消息) 【树形dp】洛谷P2015 二叉苹果树_Lamenn的博客-CSDN博客
关于状压dp:
关于数位dp:
这几天学一学dp,学完马上补