背包

dalao

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;
}
二维01背包板子

但事实上,我们可以省去一维空间,即省去第一维每一个物件$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背包差不多

在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的背包问题了(部分背包待补充)

 

posted @ 2022-07-26 15:40  little_sheep_xiaoen  阅读(51)  评论(0编辑  收藏  举报