背包问题整理

01背包问题

朴素算法

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e3+10;
int v[N];   //价值
int w[N];   //重量
int f[N][N]; //考虑前i个物品,背包容量为j时的最大价值
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>w[i]>>v[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
            f[i][j] = f[i-1][j];
            if(j >= w[i])
            f[i][j] = max(f[i-1][j],f[i-1][j-w[i]] + v[i]);
        }
    }
 
    cout<<f[n][m];
}

一维优化

f[i][j]代表前i个物品 背包容量为j时的最大价值。
所以每读入一个价值 体积 就开始第二层即可。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i = 1;i <= n; i ++)
    {
        int w,v;    //分别代表物品的体积 价值
        cin>>w>>v;
        for(int j = m;j >= w; j--)
            f[j] = max(f[j], f[j - w] + v);
    }
    cout<<f[m];
}

完全背包问题

每个物品用无数次

二维朴素算法

01背包问题中,第二层循环倒着的原因是
f[i][j] = max(f[i-1][j], f[i-1][j-w[i]+v[i])
计算f[i][j]时,需要用到f[i-1][j-w[i]]
优化成一维时,f[j-w[i]]在f]j]前面
所以如果从前往后循环,用的就是更新后的f[j-w[i]]即f[i][j-w[i]]而不是f[i-1][j-w[i]],也就是第i个物品用了多次。为了防止这种情况,01背包第二层循环倒过来

而完全背包正好迎合这种思想,故直接从前往后循环即可。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N],w[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>w[i]>>v[i];
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)
            {
                f[i][j] = f[i-1][j];
                if(j >= w[i])
                f[i][j] = max(f[i-1][j], f[i][j - w[i]] + v[i]);
            }

    cout<<f[n][m];
}

一维优化

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N];
int v[N],w[N];
int main()
{
    int n,m;
    cin>>n>>m;
    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++)
            {
               f[j] = max(f[j], f[j-w[i]] + v[i]);
            }

    cout<<f[m];
}

多重背包问题I

最简单 直接三重for循环
第三层循环循环选0~s[i]个物品 //s[i]表示第i个物品的最大数量

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int f[N];
int v[N],w[N],s[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>w[i]>>v[i]>>s[i];
    for(int i=1;i<=n;i++)
        for(int j=m;j>= w[i];j--)
        {
            for(int k=0;k <= s[i];k ++)
            {
                if(j - k * w[i] >= 0)
                f[j] = max(f[j], f[j - k * w[i]] + k * v[i]);
            }
        }
    printf("%d",f[m]);
}

多重背包问题II

题解

数据范围上升至1e3, 3重for循环会109,故需要优化

二进制优化

结合自己的笔记看

如何将一个数x合理的划分为二进制数,使这些数可以表示 0~x 之间的任意一个数

对 10 而言
1 2 4 3 是合理的划分
对 12而言
1 2 4 5 是合理的划分
然后转化为01背包问题

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 2010;
int n,m;
int f[N];

struct Good
{
    int v,w;
};
vector<Good> goods;
int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        int v,w,s;
        cin >> v >> w >> s;
        for(int k = 1; k <= s; k *= 2)
        {
            s -= k;
            goods.push_back({v * k , w * k});
        }
        if(s > 0)
            goods.push_back({v * s , w * s});
    }
    for(int i = 0 ; i < goods.size() ; i ++ )
    {
        for(int j = m ; j >= goods[i].v ;j --)
        {
            f[j] = max(f[j] , f[j - goods[i] . v ] + goods[i] . w);
        }
    }
    cout<<f[m];
}

多重背包问题III

题解

需要用到单调队列优化。类似优先队列。
不太会,他也没大讲


混合背包问题

既有01背包只能用1次的 也有完全背包用无限次的 也有多重背包有个数限制的。
将多重背包的二进制转化为01背包。
所以只有01背包和完全背包的。
二者的区别在于01背包第二层for循环倒着,完全背包的第二层for循环正着,分开讨论

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1010;
int f[N];
struct thing
{
    int kind;
    int v,w;
};
vector <thing> things;

int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int w,v,s;
        cin>>w>>v>>s;
        if(s == -1)
            things.push_back({-1,v,w});
        else if(s == 0)
            things.push_back({0,v,w});
        else    //说明是多重背包问题
        {
            for(int k = 1; k <= s; k*=2)
            {
                s -= k;
                things.push_back({-1, k * v, k * w});
            }
            if(s > 0) things.push_back({-1, s * v, s * w});
        }
    }   
    
    //01 和完全背包分开
    for(int i = 0; i< things.size(); i++)
    {
        int kind = things[i].kind, v = things[i].v , w = things[i].w;
        if(kind == -1)
        {
             for(int j = m; j >= things[i].w; j--)  //如果是01背包就从大到小枚举
                f[j] = max(f[j],f[j - w] + v);
        }
        else
        {
            for(int j = w; j <= m;j ++)             //如果是完全背包就从小到大枚举               
            	f[j] = max(f[j],f[j - w] + v);
        }
    }
    cout<<f[m];
}

二维费用的背包问题

与01背包相比,既有体积限制,又有重量限制
f[i][j]表示背包体积为 i 最大承重为 j 情况下的能放的最大价值

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N]; //f[i][j] 表示 背包体积为i 承重为j的情况下的最大价值
int main()
{
    int n,V,M;
    cin>>n>>V>>M;
    for(int i=1;i<=n;i++)
    {
        int v,m,w;//分别代表体积 重量 价值
        cin>>v>>m>>w;
        for(int j = V; j >= v; j--)     //从大到小枚举
            for(int k = M; k >= m; k--) //从大到小枚举
                f[j][k] = max(f[j][k], f[j - v][k - m] + w);
    }
    cout<<f[V][M];
}

分组背包问题

有多个组,每个组内选1个最多。
数据范围很小 只有100 可以用O(n3)
是朴素多重背包算法的一般情况,代码很像

//是多重背包的一般情况。和多重背包代码很像很像
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int f[N];
int v[N],w[N];//分别表示价值 体积

int main()
{
    int n,m;//分别表示n组物品 背包容量为m
    cin>>n>>m;
    
    for(int i=1;i<=n;i++)
    {
        int s;  //表示每组中物品的个数  每输入一组接着求出来对于该组的方案
        cin>>s;
        for(int j = 1;j <= s;j++)
        {
            cin>>w[j]>>v[j];
        }
        for(int j = m; j >= 0; j--)
            for(int k = 1; k <= s;k++)  
            {
                if(j - w[k] >= 0)
                f[j] = max(f[j], f[j - w[k]] + v[k]);   //需要用到前面的f[j - w[k]] 所以j从后往前更新
            }
    }
    cout<<f[m];
}

背包问题求方案数

需要很好的理解 什么是 状态转移
看f[i][j] 是从f[i-1][j]转移过来还是f[i-1][j-w[i]]+v[i]转移而来

/*
不能用自己最早的想法,先求一遍,求出f[n][m],再去重新求一遍,看看有多少个数 = f[n][m]
因为虽然数可能是一样的,但可能始终是一个方案,选的是同一组物品,只不过没有比他更大的,一直就是他延续下去的。
还是要看方案数,选不同的物品。
*/
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
int n,m; 
int f[N];   //f[i] 放入物品体积恰好为i情况下的最大价值
int g[N];   //g[i] 放入物品体积恰好为i的情况下的最大方案数是多少
int v[N],w[N];  //分别表示价值 体积
/*
f[i]表示体积恰好为i的情况下的最大价值 的原因:
因为01背包问题中,f[i]表示体积不超过i的情况下的最大价值,包含了0~i
因为这里是计数,所以 每个表示不同体积的 都要分开,要不会有重复
*/
int main()
{
    cin>>n>>m;
    g[0] = 1; //体积为0表示啥也不放 方案数为 1
    for(int i=1;i<N;i++) f[i] = -INF;

    for(int i=1;i<=n;i++)
    {
        int w,v;
        cin>>w>>v;
        for(int j = m; j >= w; j--)
        {
            int t = max(f[j],f[j-w] + v);
            int s = 0;
            if(t==f[j]) s+=g[j];
            if(t==f[j-w] + v) s+=g[j-w];
            s%=MOD;
            f[j] =t;
            g[j] = s;
        }
    }
    //f[m]表示体积恰好为m情况下的最大价值 但是不一定代表体积小于等于m的情况 所以需要求出最大价值
    int max_value = 0;
    for(int i=1;i<=m;i++) max_value = max(max_value, f[i]);
    int res = 0;
    for(int i=0;i<=m;i++)
        if(f[i] == max_value)
            res = (res + g[i]) % MOD;
    cout<<res;
}

背包问题求具体方案

题解

求dp的时候倒着,求方案的时候正着,然后如果前面和后面相等则输出前面的即代表字典序最小的。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N],w[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>w[i]>>v[i];
    }
    for(int i=n;i;i--)  //从后往前 写dp 可以从前往后找方案,这样后面比前面大就可以直接输出前面
    {
        for(int j=0;j<=m;j++)
        {
            f[i][j] = f[i+1][j];
            if(j >= w[i])
                f[i][j] = max(f[i+1][j], f[i+1][j - w[i]] + v[i]);
        }
    }
    
    int i=1,j=m;
    while(i <= n)
    {
        if(j >= w[i] && f[i+1][j - w[i]] + v[i] >= f[i+1][j]) //if(j >= w[i] && f[i+1][j - w[i]] + v[i] == f[i][j])
        {
            printf("%d ",i);
            j -= w[i];
            i ++;
        }
        else i++;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值