背包
01背包:
就是每件物品只能取一次的那种
板子如下:
#include<iostream> #include<cstdio> using namespace std; int n,t,y,ans; int w[10010],v[10010];//分别是代价与价值 int dp[10010][10010]; int main(){ cin >> y >> n; dp[0][0] = 0;ans = -100; for( int i = 1;i <= n;i++ ) cin >> w[i] >> v[i]; //初始化,不可能买到的就是-1 for( int i = 1;i <= y+5;i++ ) dp[0][i] = -1; for( int i = 1;i <= n;i++ ) for( int j = 0;j <= y;j++ ){ dp[i][j] = dp[i-1][j];//先延续之前 if( j >= w[i] && dp[i-1][j-w[i]] > -1 ) //先保证数组不越界,再看能不能转移 dp[i][j] = max( dp[i][j],v[i]+dp[i-1][j-w[i]] ); } for( int i = 0;i <= y;i++ ) ans = max( ans,dp[n][i] ); cout << ans; return 0; }
但事实上,我们可以省去一维空间,即省去第一维每一个物件$i$
因为我们是根据上一层有没有数值而进行修改,也可以直接转化为在上一层修改
我们值需要知道经过之前的操作,最终的结果是多少,所以可以考虑去掉一维(码量还小)
#include<iostream> #include<cstdio> using namespace std; int n,t,m,ans; int w[10010],v[10010];//分别是代价与价值 int dp[10010]; int main(){ cin >> m >> n; ans = -100; for( int i = 1;i <= n;i++ ) cin >> w[i] >> v[i]; for( int i = 1;i <= n;i++ ) for( int j = m;j >= w[i];j-- ){ //注意昂注意,第二层从m开始倒着循环的,一直循环到j = w[i] dp[j] = max( dp[j],dp[j-w[i]] + v[i] ); } cout << dp[m]; return 0; }
完全背包
就是每种物品可以无限选
事实上板子跟01背包差不多
在01背包中,$j$从$m$一直减到$w[i]$,保证了每个物品在一个$j$中只会被放一次。 而完全背包中,从$w[i]$一直加到$m$,使在可以放下的情况下尽量多地放。
#include<iostream> #include<cstdio> using namespace std; int n,t,m,ans; int w[10010],v[10010];//分别是代价与价值 int dp[10010]; int main(){ cin >> m >> n; ans = -100; for( int i = 1;i <= n;i++ ) cin >> w[i] >> v[i]; for( int i = 1;i <= n;i++ ) for( int j = w[i];j <= m;j++ ){ //只有这里不一样 dp[j] = max( dp[j],dp[j-w[i]] + v[i] ); } cout << dp[m]; return 0; }
多重背包
一、常见问题:
有$n$种物品,第$i$种价值为$w_i$,体积为$v_i$,有$C_i$件
二、直接拆分法
最暴力的方法就是将每件物品当做一个新的种类,复杂度较大
其代码实现如下:
int dp[100010]; memset( a,0xcf,sizeof(a) );//这样可以将数组初始化为-INF dp[0] = 0; for( int i = 1;i <= n;i++ ) //枚举物品种类 for( int j = 1;j <= c[i];j++ ) //暴力枚举数量 for( int k = m;k >= v[i];k-- ) //要注意,倒序遍历! f[k] = max( f[k],f[k-v[i]]+w[i] ); int ans = 0; for( int i = 1;i <= n;i++ ) ans = max( ans,f[i] );
三、二进制拆分法
众所周知,$2^0,2^1,2^2,2^3\cdot\cdot\cdot2^{k-1}$这$k$个$2$的整数次幂可以表示出$0 \sim 2^{k}$中的任何数
所以我们可以求出满足$2^0,2^1,2^2,2^3\cdot\cdot\cdot2^p \leq C_i$的最大整数$p$,如设$R_i = C_i - 2^0 - 2^1 - 2^2 - 2^3 - \cdot\cdot\cdot - 2^p$
则有:
1. $0 \sim R_i-1$ 可以用 $2^0,2^1,2^2,2^3\cdot\cdot\cdot2^p$ 组成
2. 因为$1$,所以$C_i$可以用$2^0,2^1,2^2,2^3\cdot\cdot\cdot2^p,R_i$组成
综上,我们可以将$C_i$分为$p+2$种新物品,效率较高
分组背包
模型如下:
给定N组物品,其中第$i$组有$C_i$个物品。
第$i$组的第$j$个物品体积为$V_{ij}$,价值为$W_{ij}$。
有背包体积为M,要求选择若干个物品放入背包,使得每组至多选择一个物品
并且物品总体积不可以超过M,求最大的物品价值
先上代码:
int dp[100010]; memset( dp,0xcf,sizeof(dp) );//初始化为负无穷 dp[0] = 0; for( int i = 1;i <= n;i++ ) //枚举物品组别 for( int j = m;j >= 0;j-- ) for( int k = 1;k <= c[i];k++ ) //c[i]是该组剑术 if( j >= v[i][k] ) f[j] = max( f[j],f[j-v[i][k]]+w[i][k] ); int ans = 0; for( int i = 1;i <= n;i++ ) ans = max( ans,f[i] );
代码中,以每组作为最外层遍历的FOR,
第二层枚举的是背包容积,第三层枚举第$i$组内的物品
注意了昂,其中如果第二层与第三层交换,那就类似于多重背包了
所以我们要让每一组内$c[i]$个物品的循环$k$在$j$的内层
这是因为每组最多选一个物品,如果让$k$位于外层,就会让每组物品在F数组上的转移产生积累,最终选择的数量就超过了一个
从动规的角度,$i$是“阶段”,$i,j$共同组成“状态”,$k$是“决策”
三者不可以混淆
另外,分组背包是树形DP背包问题的基本模型,如洛谷P2014
树形背包
看看板子题,洛谷P2014选课
以上就是DP的背包问题了(部分背包待补充)