其他分享
首页 > 其他分享> > carnation13的背包学习笔记

carnation13的背包学习笔记

作者:互联网

有关背包的问题是本蒟蒻学习的第一类初级算法,也是第一次接触\(dp\)所学的内容,背包问题的最常见的形式是有若干个物品,每个物品拥有体积\(c_i\)和价值\(w_i\),现在给你一个容量为\(V\)的背包,求背包能装的物品的最大价值。

01背包

二维形式:用\(f[i][v]\)表示用\(i\)件物品填充体积为\(v\)的背包得到的最大价值
状态转移方程如下:\(f[i][v]=max(f[i-1][v],f[i-1][v-c[i]]+w[i])\)
也可以得到代码写法:

for(int i=1;i<=n;i++)//枚举物品 
    for(int j=1;j<=V;j++)//枚举体积 
    //这个位置是可以正序枚举的.
    //一维01背包必须倒序
        if(j>=c[i])f[i][j]=max(f[i-1][j],f[i-1][j-c[i]]+w[i]);
        else f[i][j]=f[i-1][j].
        //上面的if语句是判断当前容量的背包能否被较小体积的背包填充得到.
        //显然 如果j-c[i]<0我们无法填充

对于\(01\)背包问题,我们还可以采取一维优化,我们不难注意到\(f[i][v]\)中的前一维只与前一个状态有关,因此可以滚动掉。
从而得到一维形式:用\(f[i]\)表示背包体积为\(i\)时得到的最大价值
状态转移方程如下:\(f[j]=max(f[j],f[j-c[i]]+w[i])\)
而由于一维形式下,当前状态会被前一个状态所影响,所以用一维形式枚举时要倒序枚举
代码写法如下:

for(int i=1;i<=n;i++)//枚举物品 
    for(int j=V;j>=c[i];j--)//倒序枚举体积 
        f[j]=max(f[j],f[j-c[i]]+w[i]);

对于\(01\)背包问题,可以拿这个板子题练练手

01背包变式

1、\(01\)背包恰好装满方案数
用\(dp[i][j]\)表示用\(i\)件物品填充体积为\(j\)的背包的方案数
代码如下

dp[1][0]=1;
for(int i=2;i<=n+1;++i)
{
    for(int j=0;j<=V;++j)//二维形式
    {
        if(j<w[i-1])dp[i][j]=dp[i-1][j];
        else dp[i][j]=dp[i-1][j]+dp[i-1][j-w[i-1]];
    }
}
cout<<dp[n+1][V];

2、\(01\)背包恰好装满的最大价值
用\(f[i]\)表示恰好装满容量为\(i\)的背包的最大价值、
代码如下

for(int i=0;i<=V;++i)f[i]=-0x7fffffff;//初始化为负无穷大
f[0]=0;
for(int i=1;i<=n;++i)
{
    for(int j=V;j>=c[i];--j)//倒序枚举
    {
        f[j]=max(f[j],f[j-c[i]]+w[i]);
        if(f[j]<0)f[j]=-0x7fffffff;
    }
}

完全背包

这里只介绍完全背包的一维写法。
它和\(01\)背包一样都是用\(f[i]\)表示背包体积为\(i\)时得到的最大价值
状态转移方程也和\(01\)背包一样:\(f[j]=max(f[j],f[j-c[i]]+w[i])\)
但是\(01\)背包和完全背包的一维代码写法不同,\(01\)背包枚举体积时是倒序枚举,而完全背包是正序枚举,这是两者的最大区别也是唯一区别
代码写法如下:

for(int i=1;i<=n;i++)//枚举物品 
    for(int j=c[i];j<=V;j++)//正序枚举体积 
        f[j]=max(f[j],f[j-c[i]]+w[i]);

同样对于完全背包也有板子题

完全背包变式

1、完全背包恰好装满方案数
用\(f[i]\)表示恰好装满容量为\(i\)的背包的方案数
代码如下

f[0]=1;
for(int i=1;i<=n;++i)
    for(int j=c[i];j<=V;++j)//正序枚举
        f[j]=f[j]+f[j-c[i]];
cout<<f[V];

2、完全背包恰好装满的最小价值
用\(f[i]\)表示恰好装满容量为\(i\)的背包的最小价值
代码如下

f[0]=0;
for(int i=1;i<=V;++i)f[i]=0x7fffffff;//和求最大值不同,求最小值应该初始化为正无穷
for(int i=1;i<=n;++i)
    for(int j=c[i];j<=V;++j)
        f[j]=min(f[j],f[j-c[i]]+w[i]);
cout<<f[V];

多重背包

只介绍主流的做法:二进制拆分
即将大物品拆分成1,2,4,8件物品的和,其中最后一件自动补齐,如13可以拆分成1,2,4,6
这样可以将物品的数量减少,得到了多个大物品,便可以去跑\(01\)背包了
二进制拆分代码如下:

for(int i=1;i<=n;i++)
{
    for(int j=1;j<=num[i];j<<=1)
    //二进制每一位枚举.
    //注意要从小到大拆分
    {
        num[i]-=j;//减去拆分出来的
        new_c[++tot]=j*c[i];//合成一个大的物品的体积
        new_w[tot]=j*w[i];//合成一个大的物品的价值
    }
    if(num[i])//判断是否会有余下的部分.
    //就好像我们某一件物品为13,显然拆成二进制为1,2,4.
    //我们余出来的部分为6,所以需要再来一份.
    {
        new_c[++tot]=num[i]*c[i];
        new_w[tot]=num[i]*w[i];
        num[i]=0;
    }
}

另一种多重背包优化方法:单调队列优化(待补)
多重背包板子题

多重背包变式

1、多重背包能否恰好装满及恰好装满的最大价值
用\(f[i]\)表示恰好装满容量为\(i\)的背包的最大价值
代码如下

for(int i=1;i<=V;++i)f[i]=-0x7fffffff;
f[0]=0;
for(int i=1;i<=n;++i)
{
    if(num[i]*w[i]>=V)
    {
        for(int j=w[i];j<=V;++j)f[j]=max(f[j],f[j-w[i]]+1);
    }
    else 
    {
        l=1,now=num[i];
        while(l<now)
        {
            for(int j=V;j>=l*w[i];--j)f[j]=max(f[j],f[j-l*w[i]]+l);
            now=-=l;
            l<<=1;
        }
        for(int j=V;j>=now*w[i];--j)f[j]=max(f[j],f[j-now*w[i]]+now);
    }
}
if(f[V]<0)cout<<"NO";//这里只列出恰好装满V是否可以以及输出最大值,也可以判断比V小的任意正整数的容量背包数据
else cout<<"YES"<<f[V];

2、多重背包恰好装满方案数(待补)

标签:背包,int,max,装满,笔记,恰好,01,carnation13
来源: https://www.cnblogs.com/carnation13/p/16103832.html