背包问题 - DP

背包问题属于线性DP,但相对难

01背包

n个物品,背包体积为m,每个物品最多拿一个,问能拿的最大价值

二维解法

  • 状态表示: f[i][j]ij
  • 集合划分: 选或不选第i个物品
if(选第i个物品) f[i - 1][j] // 即为上次的最大价值
else f[i][j - v[i]] + w[i] // 即减去此物品的体积,加上它的价值
  • 状态转移: f[i][j]=max(f[i1][j], f[i][jv[i]]+w[i])

其中v[i]表示第i个物品的体积,w[i]表示第i个物品的价值

一维解法

根据观察我们发现,每一个状态f[i][j]实际上都只会用到上一次状态f[i1][j]的值,因此舍弃第一维,将原数组作为滚动数组倒序枚举,从而降为为一维数组

  • 状态表示: f[i]j
  • 集合划分: 选或不选第i个物品
if(选第i个物品) f[j] // 即为上次的最大价值
else f[j - v[i]] + w[i] // 即减去此物品的体积,加上它的价值
  • 状态转移: f[j]=max(f[j], f[jv[i]]+w[i])

其中v[i]表示第i个物品的体积,w[i]表示第i个物品的价值

练习:T1290. 采药

AC代码

此题时间为体积,草药的价值就是价值

#include <iostream>
#include <cstring>
#include <algorithm>
#define INF 0x3f3f3f3f

using namespace std;

const int N = 1100;
typedef long long LL;

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

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

    for(int i = 1; i <= n; i++)
    {
        for(int j = V; j >= w[i]; j --)
        {
            f[j] = max(f[j], f[j - w[i]] + v[i]);
        }
    }
    
    cout << f[V];
    return 0;
}

完全背包

n个物品,背包体积为m,每个物品无限拿,问能拿的最大价值

  • 状态表示: f[i][j]ij

  • 集合划分:k个第i个物品

  • 状态转移: f[i][j]=max(f[i][j],f[i1][jkv[i]]+kw[i])

其中v[i]表示第i个物品的体积,w[i]表示第i个物品的价值,k<=j

优化

f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w ,  f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , .....)
f[i , j-v]= max(            f[i-1,j-v]   ,  f[i-1,j-2*v] + w , f[i-1,j-3*v]+2*w , .....)

发现他们之间只差了一个w,因此,原方程可以优化为f[i][j]=max(f[i][j],f[i][jv[i]]+w[i])
发现他和01背包的f[i][j]=max(f[i1][j], f[i][jv[i]]+w[i])很像,其实他也可以优化为一维,但是和01背包的概念并不同

f[j]=max(f[j],f[jv[i]]+w[i])
还是滚动数组,只不过这次是正序

练习:P1616 疯狂的采药

AC代码

#include <iostream>
#include <cstring>
#include <algorithm>
#define INF 0x3f3f3f3f

using namespace std;

const int N = 1e7+10;
typedef long long LL;

LL v[N], w[N], f[N]; // 要开long long……

int main()
{
    int V, n;
    cin >> V >> n;
    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 <= V; j ++)
        {
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    
    cout << f[V];
    return 0;
}

多重背包

n个物品,背包体积为m,每个物品最多拿s[i]个,问能拿的最大价值

  • 状态表示: f[i][j]ij

  • 集合划分:k个第i个物品

  • 状态转移: f[i][j]=max(f[i1][jv[i]k]+w[i]k,f[i][j])

其中v[i]表示第i个物品的体积,w[i]表示第i个物品的价值,k<=min(j,s[i])

暴力代码

多重背包问题 I

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 1010;
int f[N][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 = 0; j <= m; j ++ )
            for (int k = 0; k <= s[i] && v[i] * k <= j; k ++ )
                    f[i][j] = max(f[i - 1][j - v[i] * k] + w[i] * k, f[i][j]);
    cout << f[n][m] << endl;
    return 0;
}

二进制优化

多重背包不能采用完全背包的一维优化

f[i , j ] = max( f[i-1,j] ,f[i-1,j-v]+w ,f[i-1,j-2v]+2*w ,..... f[i-1,j-Sv]+S*w, )
f[i , j-v]= max( f[i-1,j-v] ,f[i-1,j-2v]+w, ..... f[i-1,j-Sv]+(S-1)*w, f[i-1,j-(S+1)v]+S*w )

为什么多出来了一项!

这是因为完全背包可以无限取,所以没有最后一项的问题

那么我们就只能采用另一种优化方式:二进制优化

  • 我们知道用20,21,22,...,2k可以表示任意2k+1内的数字(不含2k+1

那么s[i]自然可以分成20,21,22,...,2k的形式,取1 s[i]个物品就变成了,在k个物品20,21,22,...,2k中01背包,时间复杂度从O(n3)大大降低成O(n2logS)

代码

题目:多重背包问题 II

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 12010, M = 2010;
int w[N], v[N], f[M], cnt;


int main()
{
    int n, m; // n是物品数,m是容量
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) 
    {
        int a, b, s;
        cin >> a >> b >> s;
        int k = 1;
        while(k <= s) // 拆分
        {
            v[++ cnt] = k * a;
            w[cnt] = k * b;
            s -= k;
            k *= 2;
        }
        if(s > 0) // 有边角料
        {
            v[++ cnt] = s * a;
            w[cnt] = s * b;
        }
    }
    n = cnt; // 更新物品数量
    for (int i = 1; i <= n; i ++ ) // 01背包
        for (int j = m; j >= v[i]; j -- )
            f[j] = max(f[j], f[j - v[i]] + w[i]);
    cout << f[m] << endl;
    return 0;
}

其实还有一个单调队列优化,但是本蒟蒻不会╮(╯﹏╰)╭

分组背包

n个物品,背包体积为m,每个物品属于一个组,每组中最多取一个物品,问能拿的最大价值

状态表示:f[i][j]ij
状态转移: f[i][j]=max(f[i][j],f[i1][jv[i][k]]+w[i][k])

其中v[i][k]表示第i个组,第k个物品的体积,w[i][k]表示第i组,第k个物品的价值

写出代码

for(int i = 1; i <= n; i++){
    for(int j = 0; j <= m; j++){
        f[i][j] = f[i - 1][j];  //不选
        for(int k = 0; k < s[i]; k++){
            if(j >= v[i][k])   
		f[i][j] = max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);
        }
    }
}

和01背包很像,所以同理,也可以进行一维优化,因为只用到了上次的状态,所以倒序

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

例题:分组背包

代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define INF 0x3f3f3f3f

using namespace std;

const int N = 210, M = 35;
typedef long long LL;

int f[N];

struct node
{
    int w, v;
}s[M]; // 记录物品属性
vector<int> idxs[M]; // 记录组数

int main()
{
    int V, n, t;
    cin >> V >> n >> t;
    for(int i = 1; i <= n; i ++)
    {
        int p;
        cin >> s[i].v >> s[i].w >> p;
        idxs[p].push_back(i); // 放入对应组
    }

    for(int i = 1; i <= n; i ++)
    {
        for(int j = V; j >= 0; j --)
        {
            for(int k = 0; k < idxs[i].size(); k ++)
            {
                int idx = idxs[i][k]; // 取出编号
                if(j >= s[idx].v) // 背包装得下?
                    f[j] = max(f[j], f[j - s[idx].v] + s[idx].w); // 根据编号取出属性
            }
        }
    }

    cout << f[V] << endl;
    
    return 0;
}

混合背包就是混合,进行if判断,判断要用到背包类型,没什么特别之处,因此不记录

posted @   MoyouSayuki  阅读(59)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· .NET Core 中如何实现缓存的预热?
· 三行代码完成国际化适配,妙~啊~
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
:name :name
点击右上角即可分享
微信分享提示