动态规划--背包系列

 

目录

 

1.01背包

2.完全背包

3.多重背包

4.分组背包

后续文章会给出例题!

 

 


d9c99405d511455a828a26962c19b4e4.png

 

1.01背包

问题描述:

给定n个物品,每个物品仅有一个,其重量wi,价值vi,现有一容量为W的背包,求解:

在不超过背包容量的情况下,使放入背包的物品价值最大。

.1定义状态:dp[i][j]为将前i种物品放入容量为j的背包可以获得的最大价值

.2.确定状态转移方程:dp[i][j]=max(d[i-1][j-w[i]+v[i], dp[i-1][j])

解释:对于第i个物品有放(1)和不放(0)两种选择。

.3.确定边界值:由dp[i][j]的定义易知:dp[i][0]=d[0][j]=0

.4.模板代码

(该模板可以求出具体那些物品放入了背包)

int n, W;//n为物品种类数,W为背包容量
int dp[n+1][W+1];
//之后代码不在定义n, W, dp等变量
int bag(int w[], int v[]){
    for(int i=1;i<n+1;i++)
        for(int j=1;j<W+1;j++)
            if(j>=w[i]) dp[i][j]=max(dp[i-1][j-w[i]]+v[i], dp[i-1][j]);
            else dp[i][j]=dp[i-1][j];
    return dp[n][W];
}

//确认具体放入的物品
void find(int is_[],int w[]){
    for(int i=n,j=W,k=0;i>0;i--)
        if(dp[i][j]>dp[i-1][j]) is_[k++]=i,j-=w[i];
}

--空间优化

..1.滚动数组:

由状态转移方程知:dp[i][j]仅仅由dp[i-1][...]转移而来,即第i行的值只与第i-1行有关,因此仅需两行,一行用来保存(i)当前值,一行用来保存上一次(i-1)的值

int bag(int w[], int v[]){
    int dp[2][W+1]={0};
    for(int i=1;i<n+1;i++)
        for(int j=1;j<W+1;j++)
            if(j>=w[i]) dp[i%2][j]=max(dp[(i-1)%2][j-w[i]]+v[i], dp[(i-1)%2][j]);
            else dp[i%2][j]=dp[(i-1)%2][j];
    return dp[n%2][W];
}

..2.利用时间差倒推:

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

2.完全背包

问题描述:

给定n个物品,每个物品有无数个,其重量wi,价值vi,现有一容量为W的背包,求解:

在不超过背包容量的情况下,使放入背包的物品价值最大。

01背包基本一致,但注意一种物品在放入一个后,之后还可以继续放入

.1定义状态:dp[i][j]为将前i种(不是第i个)物品放入容量为j的背包可以获得的最大价值

.2.确定状态转移方程:dp[i][j]=max(d[i][j-w[i]+v[i], dp[i-1][j])

直接上优化代码:

...利用时间差正推:

这里对正推做简要解释:由状态转移方程知欲求dp[i][j]则需要先求出dp[i][j-w[i]]与dp[i-1][j],而dp[i][j-w[i]]在dp[i][j]之前,所以正推先求出dp[i][j]之前的值----这里建议画图,清晰明了。

int bag(int w[], int v[]){
    int dp[W+1]={0};
    for(int i=1;i<n+1;i++)
        for(int j=w[i];j<=W;j++)
            dp[j]=max(dp[j-w[i]]+v[i], dp[j]);
    return dp[W];
}

3.多重背包

问题描述:

给定n个物品,每个物品仅有ci个,其重量wi,价值vi,现有一容量为W的背包,求解:

在不超过背包容量的情况下,使放入背包的物品价值最大。

.法1:暴力分解:

        将ci个种类相同的物品看成不同的物品,转换为01背包问题求解(容易TLE)

int bag(int w[], int v[], int c[]){
    int dp[W+1]={0};
    for(int i=1;i<n+1;i++)
        for(int k=1;k<=c[i];k++)
            for(int j=W;j>=w[i];j--)
                dp[j]=max(dp[j-w[i]]+v[i], dp[j]);
    return dp[W];
}

.法2:二进制拆分:

        8ae16c3bea8a4347881d020451458ce8.png

  为何选择二进制拆分,不选择三进制、四进制拆分?

       因为二进制拆分出来的数之间相互组合可以取满0~c[i]之间的所有物品且个数最少

       如上图将9拆分成1,2,4,2它们之间相互组合可以取到:0(一个都不要)、1、2、...、8、9

int bag(int w[], int v[], int c[]){
    for(int i=1;i<=n;i++)
        if(w[i]*c[i]>=W)
            //转换成完全背包 --- w[i]*c[i]>=W 意味着理论上i类物品的个数足够将背包装满
            for(j=w[i];j<=W;j++)
                d[j]=max(dp[j-w[i]]+v[i], dp[j]);
        else
            for(int k=1;c[i]>0;k<<1){
                //x是二进制拆分后的类包含的个数
                //eg:9 -- 1,2,4,2
                //k:1 2 4 8 --- c[i]:9 8 6 2 k与c[i]的同步变化
                //最后k会变成8 此时余量为c[i]=2所以取最小值
                int x=min(k, c[i]);
                //转换成01背包
                for(int j=W;j>=x*w[i];j--)
                    dp[j]=max(dp[j-x*w[i]]+x*v[i], dp[j]);
                //更新余量c[i]
                c[i]-=x; 
            }
}

若只是可信性,则可以使用数组优化。

4.分组背包

问题描述:

有 N 组物品和一个容量是 W 的背包。每组物品有若干个,同一组内的物品最多只能选一个。每件物品的体积是 wij,价值是 vij,其中 i 是组号,j 是组内编号。求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大,输出最大价值。

与01背包类似,但是每次选取则考虑组内最大

.1定义状态:dp[i][j]为将前i组物品放入容量为j的背包可以获得的最大价值

.2.确定状态转移方程:dp[i][j]=max(d[i-1][j-w[i][k]]+v[i][k], dp[i][j])

int bag(int w[][..], int v[][..], int cp[]){
    for(int i=1;i<=n;i++)
        for(int j=0;j<=W;j++)
            for(int k=1;k<=c[i];k++)
                if(j>=w[i][k])
                     dp[i][j]=max(dp[i-1][j-w[i][k]]+v[i][k], dp[i][j]);

     return dp[n][W];
}

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值