01背包知识点

背包问题

01背包

NV使

状态表示

  • 集合

    所有只考虑前i个物品,且总体积不超过j的选法的合集

  • 属性

    Max

状态计算

根据最后一步划分为两类,一类为所有不选第i个物品的方法,另一类为选第i个物品的选法

得到转移方程

f[i][j]=max(f[i1][j],f[i1][jv]+w)

Code

  • 朴素写法

    for (int i = 1; i <= n; ++i)
        	for (int j = 0; j <= m; ++j)
                	if (j < v[i]) f[i][j] = f[i-1][j];
    				else f[i][j] = max(f[i - 1][j],f[i - 1][j - v[i]] + w[i]);
    
  • 空间优化

    根据转移方程,可以看出每层的状态转移最多只用到上一层的状态,所以可以将空间从O(n·m)Om,需要注意的是,此处需要逆序遍历,因为在本层更新前,数组内存储的是还是上一层的DP数值,当我们更新较大的DP数值的时候,需要用到前面的数组数值,即上一层的结果,所以可以很好的保护数据不被污染,因此此处需要逆序遍历。

    for (int i = 1; i <= n; ++i)
        	for(int j = m; j >= v[i]; --j)
    				f[j] = max(f[j-v[i]] + w[i], f[j]);
    
  • 进一步空间优化

    for (int i = 1, v, w; i <= n; ++i) {
    	std::cin >> v >> w;
    	for (int j = m; j >= v; --j)
    		f[j] = max(f[j-v[i]]+w[i], f[j]);
    }
    

完全背包问题

NV

状态表示

  • 集合

    从前i种物品中选取,且总体积不超过j的所有选法

  • 属性

    Max

状态计算

根据第i个物品选取多少个来进行集合的划分,可以得到状态转移方程。

f[i][j]=max(f[i1][jkv]+kw)(kv<=j)

Code

  • 朴素版本

    for (int i = 1; i <= n; ++i) 
        for (int j = 0; j <= m; ++j) 
            for (int k = 0; k*v[i] <= j; ++k)
                	f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
    
  • 优化过程

    规律

    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-k*v] + k*w)
    f[i-1][j-v] = max(       f[i-1][j-v],   f[i-1][j-2*v]+w ....f[i-1][j-k*v] + (k-1)*w)
    

    得到优化的转移方程

    f[i][j]=max(f[i1][j],f[i][jv]+w)

    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]>=0) f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
        }
    
  • 进一步优化

    与01背包极为相似,只在遍历顺序不同,从转移方程中可以看出我们需要的是同一层的数组元素,逆序更新的话会对数据造成污染。

    for (int i = 1, v, w; i <= n; ++i) {
        std::cin >> v >> w;
        for (int j = v; j <= m; ++j)
    		f[j] = max(f[j], f[j-v[i]]+w[i]);
    }
    

    其实想想也是,01背包每个物品只能选一个,如果从小往大选,在体积增大的时候,会造成对同一个物品选两次的可能性。

多重背包I

NVisiviwi

状态表示

  • 集合

    所有只从前i个物品里选,并且总体积不超过j的选法的集合

  • 属性

    Max

状态计算

参考01背包思路,我们对集合进行划分,每一子集为对该类物品选了若干个,每次选或不选两种操作。

即得到转移方程

f[i][j]=max(f[i1][jkv]+wk,f[i1][j])k0,1,2,3,4...si

Code

  • 朴素版本

    for (int i = 1; i <= n; ++i) 
        for (int j = 0; j <= m; ++j) 
            for (int k = 0; k*v[i] <= j; ++k)
                f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
    
  • 空间优化

    通过观察转移方程,可以看到,我们每次更新只和本层和上一层存储的数组元素有关。

    此处需要逆序操作,还是因为需要保证上一层数据的纯净。

    for (int i = 1,v ,w, s; i <= n; ++i) {
        std::cin >> v >> w >> s;
        for (int j = m; j >= v; --j)
            for (int k = 1; k <= s && k*v <= j; ++k)
                f[j] = max(f[j], f[j-k*v]+k*w);
    }
    

    f[j]表示体积为j时候的最优解。

多重背包问题Ⅱ

NVisiviwi

二进制优化

对于每种物品,以二进制的形式进行一个打包成一个新的物品,将问题转化为01背包问题,对每个新物品进行选或不选两种操作,用该方法可以完全枚举选取0si的所有选法。

Code

二进制优化+01背包一维优化

int cnt = 0;
    for (int i = 1; i <= n; ++i) {
        int a,b,s;
        cin >> a >> b >> s;
        int k = 1;
        while (k <= s) {
            cnt ++;
            v[cnt] = a * k;
            w[cnt] = b * k;
            s -= k;
            k *= 2;
        }
        if (s > 0) {
            cnt ++;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }
    n = cnt;
    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]);

多重背包Ⅲ

基于滑动窗口优化

空间优化的多重背包转移方程为

f[j]=max(f[j],f[jkv]+kw)

我们可以将它正序过来,则有以下状态

f[m]=max(f[0],f[v]+w,f[2v]+2w...f[kv+j]+kw)m=kv+j0<=j<v

不难发现,每一类中的最大值都是在同一层的DP数组中转移而来,在这里可以使用单调队列进行优化,最后求取单调队列中的最大值。

Code-O(nv)

  • 朴素写法

    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j < v[i]; ++j) {
            int hh = 0, tt = -1;
            for (int k = j; k <= m; k += v[i]) {
                while (hh <= tt && k - q[hh] > s[i] * v[i]) ++hh;
                while (hh <= tt && f[i-1][q[tt]] + (k - q[tt])/v[i])
                    q[++tt] = k;
                f[i][j] = f[i-1][q[hh]] + (k - q[hh])/v[i] * w[i];
            }
        }
    }
    
  • 拷贝数组写法

    for (int i = 1; i <= n; ++i) {
        int v, w, s; std::cin >> v >> w >> s;
        for (int j = 0; j < v; ++j) {
            int hh = 0, tt = -1;
            for (int k = j; k <= m; k += v) {
                if (hh <= tt && q[hh] <= k - s * v) ++hh;
                 while (hh <= tt && g[q[tt]] - (q[tt] - j) / v * w <= g[k] - (k - j) / v * w) --tt;
                    q[ ++ tt] = k;
                    f[k] = g[q[hh]] + (k - q[hh]) / v * w;
            }
        }
    }
    

分组背包

NVvijwi,ji,j

状态表示

  • 集合

    从前i组中选,每组从前k个物品里选且总体积小于j的所有选法

  • 属性

    Max

状态计算

分成若干组,按照01背包进行计算,每组选或不选两种情况。

  • 不选

    f[i][j]=f[i1][j]

  • f[i][j]=f[i1][jvik]+wik

Code

  • 朴素版本

    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)
                f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
        }
    }
    
  • 优化版本

     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]);
            }
        }
    
posted @   古雨梨花  阅读(43)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示