背包模型dp1:01背包,完全背包,多重背包的两大优化的详解

01背包问题:


状 态 表 示 : f [ i ] [ j ] 表 示 从 只 从 前 i 个 物 体 里 面 选 , 切 总 体 积 不 超 过 j 的 选 法 的 集 合 状态表示:f[i][j]表示从只从前i个物体里面选,切总体积不超过j的选法的集合 f[i][j]ij


集 合 划 分 : 对 于 每 个 物 体 只 能 选 和 不 选 集合划分:对于每个物体只能选和不选


状 态 转 移 方 程 i f ( j > v i ) f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v i ] ) e l s e f [ i ] [ j ] = f [ i − 1 ] [ j ] 状态转移方程if(j>v_i)f[i][j]=max(f[i-1][j],f[i-1][j-v_i])elsef[i][j]=f[i-1][j] if(j>vi)f[i][j]=max(f[i1][j],f[i1][jvi])elsef[i][j]=f[i1][j]


很 明 显 f [ i ] [ j ] 只 用 上 一 层 的 数 据 , 所 以 我 们 可 以 压 掉 一 维 并 且 逆 序 循 环 保 证 很明显f[i][j]只用上一层的数据,所以我们可以压掉一维并且逆序循环保证 f[i][j] 用 上 一 层 f [ i − 1 ] 的 数 据 更 新 的 用上一层f[i-1]的数据更新的 f[i1]


#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e4 + 10;
typedef pair<int,int> PII;
PII ch[N];
int n, maxv;
int res[N];
int main()
{
    cin >> n >> maxv;
    for(int i = 1; i <= n; ++ i)
      cin >> ch[i].first >> ch[i].second;
      
    for(int i = 1; i <= n; ++ i)
      for(int j = maxv; j >= ch[i].first; -- j)
        res[j] = max(res[j],res[j - ch[i].first] + ch[i].second);
        
    cout << res[maxv] << endl;
    
    return 0;
}

完全背包问题:


状 态 表 示 : f [ i ] [ j ] 表 示 从 只 从 前 i 个 物 体 里 面 选 , 切 总 体 积 不 超 过 j 的 选 法 的 集 合 状态表示:f[i][j]表示从只从前i个物体里面选,切总体积不超过j的选法的集合 f[i][j]ij


集 合 划 分 : 对 于 每 个 物 体 有 选 1..... + ∞ [ 体 积 上 限 ] 集合划分:对于每个物体有选1.....+\infty[体积上限] 1.....+[]


状 态 转 移 方 程 f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − k ∗ v i ] ) + k ∗ w i k ∈ [ 0 , s ] 状态转移方程f[i][j]=max(f[i-1][j-k*v_i])+k*w_ik\in[0,s] f[i][j]=max(f[i1][jkvi])+kwik[0,s]


优 化 : f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − k ∗ v i ] ) + k ∗ w i ∣ k ∈ [ 0 , s ] 优化:f[i][j]=max(f[i-1][j-k*v_i])+k*w_i|k\in[0,s] f[i][j]=max(f[i1][jkvi])+kwik[0,s]
f [ i ] [ j − v ] = m a x ( f [ i − 1 ] [ j − k ∗ v i ] ) + ( k − 1 ) ∗ w i ∣ k ∈ [ 1 , s ] f[i][j-v]=max(f[i-1][j-k*v_i])+(k-1)*w_i|k\in[1,s] f[i][jv]=max(f[i1][jkvi])+(k1)wik[1,s]
对 比 两 个 表 达 式 : 对比两个表达式:
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i ] [ j − v ] + w ) f[i][j]=max(f[i-1][j],f[i][j-v]+w) f[i][j]=max(f[i1][j],f[i][jv]+w)
很 明 显 更 新 当 前 数 据 只 要 前 层 f [ i ] 的 j 体 积 比 较 小 的 所 以 应 该 从 小 到 大 枚 举 体 积 。 很明显更新当前数据只要前层f[i]的j体积比较小的所以应该从小到大枚举体积。 f[i]j
第 一 维 物 体 , 第 二 维 循 环 体 积 , 第 三 维 循 环 决 策 第一维物体,第二维循环体积,第三维循环决策


#include <iostream>
#include <algorithm>

using namespace std;
typedef pair<int,int> PII;
const int N = 1e4 + 10;
PII c[N];
int n, maxv;
int res[N];
int main()
{
    cin >> n >> maxv;
    
    for(int i = 1; i <= n; ++ i) 
      cin >> c[i].first >> c[i].second;
      
    for(int i = 1; i <= n; ++ i)
      for(int j =c[i].first; j <= maxv; ++ j)
        res[j] = max(res[j],res[j - c[i].first] + c[i].second);
        
    cout << res[maxv] << endl;
       
}

多重背包[二进制优化版本]


状 态 表 示 : f [ i ] [ j ] 表 示 从 只 从 前 i 个 物 体 里 面 选 , 切 总 体 积 不 超 过 j 的 选 法 的 集 合 状态表示:f[i][j]表示从只从前i个物体里面选,切总体积不超过j的选法的集合 f[i][j]ij


集 合 划 分 : 对 于 每 个 物 体 有 选 1..... + ∞ [ a [ i ] [ 物 体 个 数 有 限 ] ] 集合划分:对于每个物体有选1.....+\infty[a[i][物体个数有限]] 1.....+[a[i][]]


我 们 可 以 将 a [ i ] 进 行 二 进 制 拆 分 , 因 为 一 个 数 可 以 分 成 若 干 个 数 之 和 。 这 样 就 变 成 01 背 包 了 我们可以将a[i]进行二进制拆分,因为一个数可以分成若干个数之和。这样就变成01背包了 a[i]01


在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;
typedef pair<int,int> PII;
const int N = 200;
int res[N];
int v[N], w[N], num[N];
int n, maxv;
int main()
{
    cin >> n >> maxv;
    
    
    for(int i = 1; i <= n; ++ i)
     cin >> v[i] >> w[i] >> num[i];
     
    vector<PII> goods;
    
    for(int i = 1; i <= n; ++ i)
    {
        for(int k = 1; k <= num[i]; k *= 2)
        {
            num[i] -= k;
            goods.push_back({v[i] * k, w[i] * k});
        }
        if(num[i] > 0)
          goods.push_back({v[i] * num[i],w[i] * num[i]});
    }
    
    for(auto it : goods)
    {
        for(int j = maxv; j >= it.first; j --)
        res[j] = max(res[j],res[j - it.first] + it.second);
    }
    
    cout << res[maxv] << endl;
    
    return 0;
}

多重背包[单调队列优化]

注释:w[i]是物体体积,v[i]是价格,num[i]是物体个数


我们就看一下原始的公式
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j − k ∗ w [ i ] ] + k ∗ v [ i ] ) k ∈ [ 0 , m i n ( j / w [ i ] , n u m [ i ] ) ] dp[i][j]=max(dp[i-1][j-k*w[i]]+k*v[i])k\in[0,min(j/w[i],num[i])] dp[i][j]=max(dp[i1][jkw[i]]+kv[i])k[0,min(j/w[i],num[i])](体积有限要取min)


我们进行数学转化
我 们 设 j = a ∗ w [ i ] + m o d 我们设j=a*w[i]+mod j=aw[i]+mod
d p [ i ] [ a ∗ w [ i ] + m o d ] = m a x ( d p [ i − 1 ] [ a ∗ w [ i ] + m o d − k ∗ w [ i ] ] + k ∗ v [ i ] ) ; dp[i][a*w[i]+mod]=max(dp[i-1][a*w[i]+mod-k*w[i]]+k*v[i]); dp[i][aw[i]+mod]=max(dp[i1][aw[i]+modkw[i]]+kv[i]);
合 并 同 类 项 合并同类项
d p [ i ] [ a ∗ w [ i ] + m o d ] = m a x ( d p [ i − 1 ] [ ( a − k ) ∗ w [ i ] + m o d ] + k ∗ v [ i ] ) ; dp[ i ][ a*w[i]+mod] = max( dp[ i-1 ][ (a-k)*w[i]+mod] + k*v[i] ); dp[i][aw[i]+mod]=max(dp[i1][(ak)w[i]+mod]+kv[i]);
最关键的一步:令 t = a − k 则 k = a − t t=a-k则k=a-t t=akk=at
化 简 得 : d p [ i ] [ a ∗ w [ i ] + m o d ] = m a x ( d p [ i − 1 ] [ t ∗ w [ i ] + m o d ] ) + ( a − t ) ∗ v [ i ] ; 化简得:dp[ i ][ a*w[i]+mod] = max( dp[ i-1 ][ t*w[i]+mod] ) + (a-t)*v[i]; dp[i][aw[i]+mod]=max(dp[i1][tw[i]+mod])+(at)v[i];
移 项 得 : d p [ i ] [ a ∗ w [ i ] + m o d ] − a ∗ v [ i ] = m a x ( d p [ i − 1 ] [ t ∗ w [ i ] + b ] − t ∗ v [ i ] ) 移项得:dp[i][ a*w[i]+mod] - a*v[i] = max( dp[ i-1 ][ t*w[i]+b ] - t*v[i]) dp[i][aw[i]+mod]av[i]=max(dp[i1][tw[i]+b]tv[i])


这 时 候 发 现 这 等 式 两 端 是 同 型 的 这时候发现这等式两端是同型的
1. 注 意 这 里 的 a 和 n u m [ i ] 是 没 有 关 系 的 , 因 为 a 是 j 拆 成 a ∗ w [ i ] + m o d 形 式 上 的 一 个 系 数 1.注意这里的a和num[i]是没有关系的,因为a是j拆成a*w[i]+mod形式上的一个系数 1.anum[i]ajaw[i]+mod
2. 我 们 再 看 看 t , t = a − k , 因 为 k 是 变 化 的 。 那 么 t 也 是 变 化 的 2.我们再看看t,t=a-k,因为k是变化的。那么t也是变化的 2.t,t=ak,kt


对于一个 d p [ i ] [ j ] 我 们 可 以 通 过 枚 举 m o d 和 a 去 取 遍 ∈ [ 0 , j ] dp[i][j]我们可以通过枚举mod和a去取遍\in[0,j] dp[i][j]moda[0,j]中的所有值当 a a a确定了之后对于 t 就 是 从 ∈ [ a − m i n ( j / w [ i ] , n u m [ i ] ) , a ] t就是从\in[a-min(j/w[i],num[i]),a] t[amin(j/w[i],num[i]),a]很明显 t t t这个"|变化的区间长度是固定的等于k|"这样就类似滑动窗口在固定长度里面求最大值,用单调队列维护就可以 O ( 1 ) O(1) O(1)转移了


#include <iostream>
#include <cstdio>
#include <stack>
#include <sstream>
#include <vector>
#include <map>
#include <cstring>
#include <deque>
#include <cmath>
#include <iomanip>
#include <queue>
#include <algorithm>
#include <set>
#define mid ((l + r) >> 1) 
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define ms(a,al) memset(a,al,sizeof(a))
#define log2(a) log(a)/log(2)
#define _for(i,a,b) for( int i = (a); i < (b); ++i)
#define _rep(i,a,b) for( int i = (a); i <= (b); ++i)
#define for_(i,a,b) for( int i = (a); i >= (b); -- i)
#define rep_(i,a,b) for( int i = (a); i > (b); -- i)
#define lowbit(x) ((-x) & x)
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define INF 0x3f3f3f3f
#define hash Hash
#define next Next
#define count Count
#define pb push_back
#define f first
#define s second
using namespace std;
const int N = 1e5+10;
const double eps = 1e-10;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
template<typename T> void read(T &x)
{
    x = 0;char ch = getchar();ll f = 1;
    while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
    while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
template<typename T, typename... Args> void read(T &first, Args& ... args) 
{
    read(first);
    read(args...);
}
int n, m;
int w[N], v[N], num[N];
int dp[1100][20010];
int q[N];
int main()
{
    read(n,m);
    _rep(i,1,n)
      read(w[i],v[i],num[i]);
    //枚举物体个数;
    _rep(i,1,n)
    {
        _rep(mod,0,w[i]-1)//本来是j,我们变成枚举mod和k;
        {
            int hd = 1, tl = 0;
            for(int k = 0; k*w[i] + mod <= m; ++ k)
            {
                int now = dp[i - 1][k * w[i] + mod] - k*v[i];
                while(k - q[hd] > min(num[i],(k*w[i]+mod)/w[i]) && hd <= tl) hd ++;//单调队列里面储存的是idx不是dp值,如果idx和当前的差值超了就pop掉
                while(dp[i - 1][q[tl]*w[i] + mod] - q[tl]*v[i]<=now && hd <= tl) tl--;
                q[++ tl] = k;
                dp[i][k*w[i] + mod] = dp[i - 1][q[hd]*w[i] + mod] - q[hd]*v[i] + k*v[i];
            }
        }
    }
    
    cout << dp[n][m] << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值