背包九讲

以下模板题目都可在acwing找到

一、01背包问题

之前有博客写过01背包和完全背包的详解:
背包问题

需要注意的就是01背包用的是上一层的 f 数组,所以对体积的循环是从大到小,而完全背包是从小到大。

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 1010;

int n,m;
int f[N],v[N],w[N];

//int f[N][N],v[N],w[N];

int main()
{
    cin>>n>>m;

    for(int i = 1; i <= n; i ++ )
        cin>>v[i]>>w[i];

    //二维做法
    /*for(int i = 1; i <= n; i ++ )
        for(int j = 0; j <= m; j ++ )
        {
            f[i][j] = f[i - 1][j];
            if(j >= v[i])
            f[i][j] = max(f[i][j],f[i - 1][j - v[i]] + w[i]);
        }
    cout<<f[n][m];*/

    //一维优化

    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]);

    cout<<f[m];

    return 0;
}

二、完全背包问题

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 100010;

int f[N],v[N],w[N],n,m;

int main()
{
    cin>>n>>m;
    for(int i = 1; i <= n; i ++ )
        cin>>v[i]>>w[i];

    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]);

    cout<<f[m];

    return 0;
}

三、多重背包问题Ⅰ

与01背包不同的地方在于它的每个物品都有一定的数量不止一个。
由于数据范围都小于100.
最朴素的做法对每个物品的每一个都进行一次枚举,复杂度n * m * k.

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 110;

int f[N],v[N],w[N],s[N],n,m;

int main()
{
    cin>>n>>m;
    for(int i = 1; i <= n; i ++ )
        cin>>v[i]>>w[i]>>s[i];

    for(int i = 1; i <= n; i ++ )
        for(int j = m; j >= v[i]; j --)//由于是01背包的模板所以从大到小
            for(int k = 1; k <= s[i] && k * v[i] <= j; k ++ )//每个物品的每一个都枚举
                f[j] = max(f[j],f[j - v[i] * k] + w[i] * k);

    cout<<f[m];

    return 0;
}

四、多重背包问题Ⅱ

数据范围来到了1000,用朴素做法会超时,所以对朴素做法进行一个二进制优化,对每一种物品的 s 个数量,分别分成1 2 4 8 …这样的物品再分别使用01背包模板。分完堆总共有n * lgk 堆,复杂度 n * lgk * m.

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 2010;

int f[N],w[N],v[N],s[N],n,m;

struct goods
{
    int v,w;
};

int main()
{
    vector<goods>good;

    cin>>n>>m;
    for(int i = 1; i <= n; i ++ )
        cin>>v[i]>>w[i]>>s[i];

    for(int i = 1; i <= n; i ++ )
    {
        for(int k = 1; k <= s[i]; k *= 2)
        {
            s[i] -= k;
            good.push_back({k * v[i],k * w[i]});//分堆并加入数组
        }
        if(s[i])//如果不是二的整数次幂会多出一堆
            good.push_back({s[i] * v[i],s[i] * w[i]});
    }

    for(auto i : good)//对每一堆进行01背包模板
        for(int j = m; j >= i.v; j -- )
            f[j] = max(f[j],f[j - i.v] + i.w);

    cout<<f[m];

    return 0;
}

五、多重背包问题Ⅲ

数据范围再加大,普通的二进制优化已经不能满足题目的要求了。
这里采用的是单调队列优化,代码已经写了注释,自己理解的不是很清楚具体思路可以看多重背包问题 III 详解 + yxc大佬代码解读

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 20010;

int pre[N],dp[N],q[N];//pre表示 i- 1 层的最大价值 dp表示 i 层的最大价值 q队列中存的是体积
//是一个单调递减队列,队头表示最大价值的体积
int n,m;

int main()
{
    cin>>n>>m;
    for(int i = 1; i <= n; i ++ )//枚举每一层
    {
        memcpy(pre,dp,sizeof(dp));//复制上一层结果

        int v,w,s;
        cin>>v>>w>>s;

        for(int j = 0; j < v; j ++ )//分别 j 个单调队列进行操作 (j + k * v)
        {
            int head = 0,tail = -1;

            for(int k = j; k <= m; k += v)
            {
                while(head <= tail && k - q[head] > s * v)//枚举到的体积和队头体积之差除以 v 大于 s 表示超过了数量
                    head ++;//队头出队

                //单调递减队列,所以队尾比要入队的元素价值小的话2出队
                while(head <= tail && pre[q[tail]] - q[tail]/ v * w <= pre[k] - k / v * w)
                    tail -- ;

                if(head <= tail)
                    dp[k] = pre[q[head]] + (k - q[head]) / v * w;// 更新最大值(没有很明白)

                q[ ++ tail] = k;//进队
            }
        }
    }

    cout<<dp[m];
    return 0;
}

六、混合背包问题

将01背包、完全背包、多重背包放一起的缝合怪,数据1000,多重背包用二进制优化可以过。将多重背包用二进制优化分成堆就和01背包一样操作,对于完全背包将体积循环反过来即可。

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 1010;

int n,m,f[N];

struct good//结构体存
{
    int kind;
    int v,w;
};

int main()
{
    vector<good>goods;

    cin>>n>>m;
    for(int i = 1; i <= n; i ++ )
    {
        int v,w,s;
        cin>>v>>w>>s;

        if(s < 0)//01背包
            goods.push_back({-1,v,w});

        else if(!s)//完全背包
            goods.push_back({0,v,w});

        else//分堆再01背包
        {
            for(int k = 1; k <= s; k *= 2)
            {
                s -= k;
                goods.push_back({-1,v * k,w * k});
            }
            if(s)
                goods.push_back({-1,v * s,w * s});
        }
    }

    for(auto i : goods)
    {
        if(i.kind == -1)
            for(int j = m; j >= i.v; j -- )
                f[j] = max(f[j],f[j - i.v] + i.w);

        else
            for(int j = i.v; j <= m; j ++ )
                f[j] = max(f[j],f[j - i.v] + i.w);
    }

    cout<<f[m];

    return 0;
}

七、二维费用的背包问题

除体积限制之外有多了一个重量限制,多加一个循环即可。

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 1100;

int n,M,l,v[N],m[N],w[N],f[110][110];

int main()
{
    cin>>n>>M>>l;
    for(int i = 1; i <= n ; i ++ )
        cin>>v[i]>>m[i]>>w[i];

    for(int i = 1; i <= n; i ++ )
        for(int j = M; j >= v[i]; j -- )
            for(int k = l; k >= m[i]; k -- )
                f[j][k] = max(f[j][k],f[j - v[i]][k - m[i]] + w[i]);

    cout<<f[M][l];

    return 0;
}

八、分组背包问题

多加一个从每组里选一个的循环即可

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 110;

int v[N],w[N],f[N],n,m;

int main()
{
    int n,m;
    cin>>n>>m;
    
    for(int i = 1; i <= n; i ++ )
    {
        int x;
        cin>>x;
        
        for(int j = 1; j <= x; j ++ )
            cin>>v[j]>>w[j];
            
        for(int j = m; j >= 0; j --)
            for(int k = 1; k <= x; k ++ )//每组选一个
                if(j >= v[k])
                    f[j] = max(f[j],f[j - v[k]] + w[k]);
    }

    cout<<f[m];

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值