背包dp笔记

背包DP笔记

背包是线性DP中一类重要而特殊的模型,下面分几个板块讲述。

0/1背包

0/1背包问题模型如下:

给定N个物品,其中第i个物品的体积为vi,价值为wi。有一容积为M的背包,要求选择一些物品放入背包,是得物品总体积不超过M的前提下,物品的价值总和最大。

根据以前线性DP的知识,很容易想到依次考虑每个物品是否放入背包,用“已经处理的物品数”作为DP的“阶段”,以“背包中已经放入物品的总体积”作为附加维度。

F[i,j]表示从前i个物品中选出总体积为j的物品放入背包,物品的最大价值和。

显然,状态转移方程如下:

F[i][j]=max{F[i-1][j]——不选第i个物品F[i-1][jvi]+wi选第i个物品

边界情况:F[0,0]=0,其余均为负无穷。目标:max0jM{F[N][j]}

时间复杂度O(NM)

关键代码如下:

memset(f,0x3f,sizeof(f));
f[0][0]=0;
for(int i=1;i<=n;i++){
    for(int j=0;j<=m;j++)
        f[i][j]=f[i-1][j];
    for(int j=v[i];j<=m;j++)
        f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
}

滚动数组优化:注意循环枚举的顺序

int f[MAX_M+1];
memset(f,0x3f,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++){
    for(int j=m;j>=v[i];j--){
        f[j]=max(f[j],f[j-v[i]]+w[i]]);
    }
}
int ans=0;
for(int i=0;i<=m;i++)ans=max(ans,f[i]);

例题:数字组合

给定 N个正整数 A1,A2,,AN,从中选出若干个数,使它们的和为M,求有多少种选择方案。

数据范围:1N100

1M10000

1Ai1000

这是一个典型的0/1背包模型,N个正整数就是N个物品,M就是背包的容积。在外层循环到i时(表示从前i个数中选),设F[j]表示“和为j”有多少种方案。在具体实现中,只需要把上面代码中求max的函数改为求和即可。

核心代码如下:

int f[MAX_M+1];
memset(f,0,sizeof(f));
f[0]=1;
for(int i=1;i<=n;i++){
    for(int j=m;j>=a[i];j--){
        f[j]+=f[j-a[i]];
    }
}
cout<<f[m]<<endl;

完全背包

完全背包问题模型如下:

给定N物品,其中第i个物品的体积为vi,价值为wi,并且有无数个。有一容积为M的背包,要求选择若干个物品放入背包,是得物品总体积不超过M的前提下,物品的价值总和最大。

先来考虑使用传统的二维线性DP的做法,设F[i,j]表示从前i物品中选出了总体积为j的物品放入背包,物品的最大价值和。

F[i][j]=max{F[i-1][j]——尚未选过第i种物品F[i-1][jvi]+wi(ifjvi)从第i种物品中选一个

初值:F[0,0]=0,其余均为负无穷。目标:max0jM{F[N][j]}

0/1背包一样。我们也可以省略F数组的i这一维。根据我们在0/1背包中对循环顺序的分析,当采用正序循环时,就对应着每种物品可以用无限次,也对应着F[i,j]=F[i,jvi]+wi这个在两个均处于i阶段的状态之间进行转移的方程。 关键代码如下:

int f[MAX_M+1];
memset(f,0xcf,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++)
    for(int j=v[i];j<=m;j++)
        f[j]=max(f[j],f[j-v[i]]+w[i]);
int ans=0;
for(int j=0;j<=m;j++)
    ans=max(ans,f[j]); 

例题:

给定一个自然数N,要求把N拆分成若干个正整数相加的形式,参与加法运算的数可以重复。求拆分的方案数mod 2147483648的结果。1N4000

这是一个典型的完全背包模型,1NN个自然数构成N种物品,每种物品都可以无限次使用,背包容积也是N。与上一题类似,本题也是要求方案数,我们在完全背包程序模板的基础上,把求max的函数改为求和即可。

f[0]=1;
for(int i=1;i<=n;i++)
    for(int j=i;j<=n;j++)
       f[j]=(f[j]+f[j-i])%mod;
printf("%lld\n",f[n]%mod-1);

多重背包

多重背包问题模型如下:

给定N物品,其中第i种物品的体积为vi,价值为wi,并且有ci个。有一容积为M的背包,要求选择若干个物品放入背包,是得物品总体积不超过M的前提下,物品的价值总和最大。

直接拆分法

求解多重背包问题最直接的方法就是把第i中物品看作独立的ci个物品,转化为i=1Nci个物品的0/1背包问题进行计算,时间复杂度为O(Mi=1Nci)。该算法把每种物品拆分成了ci个,效率较低。

unsigned int f[MAX_M+1];
memset(f,0xcf,sizeof(f));
f[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=0;i<=m;i++)ans=max(ans,f[i]);

二进制拆分法

众所周知,从20,21,22,...,2k1k2的整数次幂选出若干个相加,可以表示出02k1之间的任何整数。进一步地,我们求出满足20,21,22,...,2p ci的最大的整数p,设ri=ci2021...2p,那么:

1.根据p的最大性,有20,21,22,...,2p+1>ci,可推出2p+1>ri,因此20,21,22,...,2p选出若干个相加可以表示出0ri之间的任意整数。

2.从20,21,22,...,2p以及ri中选出若干个相加,可以表示出riri+2p+11之间的任何整数,根据ri的定义,ri+2p+11=ci,因此20,21,22,...,2p,ri选出若干个相加可以表示出rici之间的任何整数。

综上所述,,我们可以把数量为ci的第i种物品拆成p+2个物品,它们的体积分别为:

20vi,21vi,22vi,...,2pvi,rivi

p+2个物品可以凑成0civi之间所有能被vi整除的数,并且不能凑成大于civi的数。这等价于原问题中体积为vi的物品可以使用0ci次。该方法仅把每种物品拆成了O(logci)个,效率较高。

posted @   CQWYB  阅读(6)  评论(1编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示