文章目录
闫氏dp分析法
学算法就上AcWing,y总强!哈哈
用该方法解决dp问题时简介明了,即从集合角度思考dp问题。以下用01背包问题举例说明
在思考一个DP问题时可以从状态表示和状态计算两个角度思考问题,要从集合的角度思考问题即每一种状态代表一类情况。该方法的每个名词解释如下:
集合:即要考虑集合下标有几维组成(一般有低维到高维依次考虑),并确定每个维度代表的意义。
属性:每种状态存储值的意义,一般有max、min、数量这三种。
状态计算:将集合分为不同情况的几块并可以求出每块的值(可曲线救国),在从所有部分中求解最终值。
背包DP
背包模型是动态规划中很经典的一类模型,其问题一般可以描述为要购买多种物品求其最大价值、最大价值方案数、具体方案等。每个物品可能拥有多种费用(例如购买时对总体积、总重量、总价格有限制,即可抽象为三种费用),目标为总价值最高。一般背包问题编码思路如下:
初始化
for 枚举物品
for 枚举费用
枚举决策
输出答案
根据购买同种商品数量和商品组合限制可将背包模型分为以下5种类型。
01背包
顾名思义01背包只有两种状态,即买和不买。
例题:01背包问题
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。
一般背包问题均可降维储存,但这里为了便于理解只给出朴素版,降维储存请看空间优化
const int N=1009;
int dp[N][N];
int main()
{
int n,m;
cin>>n>>m;
int vi,wi;
for(int i=1;i<=n;i++)
{
cin>>vi>>wi;
for(int j=1;j<=m;j++)
{
dp[i][j]=dp[i-1][j];
if(j-vi>=0)
dp[i][j]=max(dp[i][j],dp[i-1][j-vi]+wi);
}
}
cout<<dp[n][m];
return 0;
}
例题及其变种:
1、普通01背包: acwing: 01背包问题
2、二维费用问题: acwing: 1022. 宠物小精灵之收服
3、方案数问题: acwing: 278. 数字组合
4、贪心+dp: acwing :能量石
完全背包
方案数问题: acwing: 1023. 买书
贪心+完全背包: acwing: 532. 货币系统
多重背包
组合背包
混合背包
几种常见问题
状态的费用限制
对于一种状态的费用有三种情况,费用不大于j的方案、费用等于j的方案、费用最小为j的情况。对于这三种情况的区别这里以01背包为例,其它背包问题大同小异。
1、体积不超过v时
例题:01背包问题
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。
分析:该题为01背包问题,f(i, j)表示在使用前 i 种物品时体积不超过 j 的最大值,在编码时无需特殊操作,初始化全为0即可
//注意这里为朴素写法没有做空间优化,dp数组为全局变量默认全部的值为0。
#include<bits/stdc++.h>
using namespace std;
const int N=1009;
int dp[N][N];
int main()
{
int n,m;
cin>>n>>m;
int vi,wi;
for(int i=1;i<=n;i++)
{
cin>>vi>>wi;
for(int j=1;j<=m;j++)
{
dp[i][j]=dp[i-1][j];
if(j-vi>=0)
dp[i][j]=max(dp[i][j],dp[i-1][j-vi]+wi);
}
}
cout<<dp[n][m];
return 0;
}
2、体积等于v时
例题: acwing1023. 买书
小明手里有n元钱全部用来买书,书的价格为10元,20元,50元,100元。
问小明有多少种买书方案?(每种书可购买多本)
分析:该题为01背包问题求方案数问题,dp(i , j)定义为使用前i中货币恰好能组成 j 面值的数量。初始化时只将合法状态置为合法值集合,不合法状态置为不合法值即可(一般但求最大值时,不合法值为-INF反之亦然,求方案数时置为1)
#include<bits/stdc++.h>
using namespace std;
const int N=1009;
int dp[5][N];
int v[5]={0,10,20,50,100};
int main()
{
int n;
cin>>n;
dp[0][0]=1//注意初始化
for(int i=1;i<=4;i++)
{
for(int j=0;j<=n;j++)
{
dp[i][j]=dp[i-1][j];
if(j-v[i]>=0) dp[i][j]+=dp[i][j-v[i]];
}
}
cout<<dp[4][n];
return 0;
}
3、体积最小为v时
例题: acwing1020. 潜水员
潜水员为了潜水要使用特殊的装备。他有一个带2种气体的气缸:一个为氧气,一个为氮气。
让潜水员下潜的深度需要各种数量的氧和氮。潜水员有一定数量的气缸。
每个气缸都有重量和气体容量。潜水员为了完成他的工作需要特定数量的氧和氮。
他完成工作所需气缸的总重的最低限度的是多少?
分析:首先该题为二维费用问题,费用状态应该定义为最小使用数量。dp(i ,j ,k)表示在前 i 个气缸中选择氧气体积至少为 j ,氮气体积至少为 k 的最小重量。只需要在计算状态转移时将选择的负值状态置为0即可
#include<bits/stdc++.h>
using namespace std;
int dp[1009][29][90];
int main()
{
int m,n,k;
cin>>m>>n>>k;
memset(dp,0x3f,sizeof dp);//注意初始化
dp[0][0][0]=0;
int ai,bi,ci;
for(int i=1;i<=k;i++)
{
cin>>ai>>bi>>ci;
for(int j=0;j<=m;j++)
{
for(int l=0;l<=n;l++)
{
int &x=dp[i][j][l];
x=dp[i-1][j][l];
x=min(x,dp[i-1][max(0,j-ai)][max(0,l-bi)]+ci);//负值置0
}
}
}
cout<<dp[k][m][n];
return 0;
}
方案数以及具体方案
关于求解方案数的问题大致分为,求解最大价值的方案数量、求解某个费用的方案数量、求解最大价值方案的具体组合
1、最大最大价值方案数量
例题: acwing:11. 背包问题求方案数
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 最优选法的方案数。注意答案可能很大,请输出答案模 109+7 的结果。
分析:该题为01背包问题,首先需要求解出体积为 v 时的最大价值,在求解最大价值时维护一个数组记录转移状态即可。dp(i , j)表示只使用前 i 个物品,体积最大为 j 时的最大价值。g(i , j)表示只使用前 i 个物品,体积最大为 j 价值为dp(i , j)方案数量。
#include<bits/stdc++.h>
using namespace std;
const int N=1009,MOD=1e9+7;
int dp[N][N],g[N][N];
int main()
{
int n,v;
cin>>n>>v;
for(int i=0;i<=n;i++) g[i][0]=1;
for(int i=1;i<=n;i++)
{
int vi,wi;
cin>>vi>>wi;
for(int j=0;j<=v;j++)
{
dp[i][j]=dp[i-1][j];
g[i][j]=max(1,g[i-1][j]);
if(j-vi>=0)
{
dp[i][j]=max(dp[i][j],dp[i-1][j-vi]+wi);
if(dp[i-1][j-vi]+wi==dp[i-1][j]) g[i][j]=(g[i-1][j-vi]+g[i-1][j])%MOD;
if(dp[i-1][j-vi]+wi>dp[i-1][j]) g[i][j]=g[i-1][j-vi];
}
}
}
cout<<g[n][v];
return 0;
}
2、某个费用的方案数量
例题: acwing: 278. 数字组合
给定 N 个正整数 A1,A2,…,AN,从中选出若干个数,使它们的和为 M,求有多少种选择方案。
分析:该题时01背包模型,dp(i , j)表示只在前 i 个数中选择,值为 j 的方案个数。只需要dp数组0层合法状态置为1,状态转移时将每个方向上的方案数相加即可。
#include<bits/stdc++.h>
using namespace std;
int dp[109][10009],w[109];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i];
for(int i=0;i<=n;i++) dp[i][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
dp[i][j]=dp[i-1][j];
if(j-w[i]>=0) dp[i][j]+=dp[i-1][j-w[i]];
}
}
cout<<dp[n][m];
return 0;
}
3、求解最大价值的具体方案组合
例题: acwing:12. 背包问题求具体方案
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1…N。
分析: 首先该题为01背包问题,让输出最小字典序的一个最大价值方案。这里我们可以采用反推状态转移的方法求解(注意字典序不是越长就越大)
#include<bits/stdc++.h>
using namespace std;
const int N=1009;
int dp[N][N];
int v[N],w[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=n;i>0;i--)
{
for(int j=1;j<=m;j++)
{
dp[i][j]=dp[i+1][j];
if(j-v[i]>=0) dp[i][j]=max(dp[i][j],dp[i+1][j-v[i]]+w[i]);
}
}
int j=m;
for(int i=1;i<=n;i++)
{
if(j-v[i]>=0&&dp[i][j]==dp[i+1][j-v[i]]+w[i])
{
j=j-v[i];
cout<<i<<' ';
}
}
return 0;
}
路径DP
序列DP
状态机DP
在某一类的DP问题中有很多状态类型并由一定的转换关系,这时我们就可以考虑能否采用状态机模型。
给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i 天的价格。设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
分析:受首先可以发现该题,有三个状态每种状态都有对应的转移方式,故这里可以采用状态机DP的求解方式求解。状态转移图如下:
#include<bits/stdc++.h>
using namespace std;
int dp[100009][3];
int main()
{
int n;
cin>>n;
int x;
memset(dp,-0x3f,sizeof dp);
dp[0][2]=0;
for(int i=1;i<=n;i++)
{
cin>>x;
dp[i][0]=max(dp[i-1][2]-x,dp[i-1][0]);
dp[i][1]=dp[i-1][0]+x;
dp[i][2]=max(dp[i-1][2],dp[i-1][1]);
}
cout<<max(dp[n][1],dp[n][2]);
return 0;
}
例题及其变种:
1、acwing:1057. 股票买卖 IV
2、acwing:1052. 设计密码