背包dp

一. 背包DP

1. 0/1背包:

要素:
  • 每个物品只有选一次或不选两种选择
  • n个物品 ,每个物品只有一件,第i个物品体积为vi,价格pi,现在有一个体积为V的背包,选出若干件物品使背包里价值最大
  • 装入第i件物品已用体积为j时,有两种选择:
    1.不放入第i件物品:f[i][j]=f[i-1][j]
    2. 放入第i件物品:f[i][j]=f[i-1][j-v[i]]+c[i]

状态转移方程: f[j] = max(f[j], f[j-v[i]] + w[i]);

代码模板如下:

例:

输入样例

 4 5
 1 2
 2 4
 3 4
 4 5

输出样例

 8
代码如下:
#include<bits/stdc++.h>
using namespace std;

const int MAX = 1050;

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

int main(){

    scanf("%d%d",&N, &V);
    for(int i=1; i<=N; i++){
        scanf("%d%d",&v[i], &w[i]); 
    }

    for(int i=1; i<=N; i++){
        for(int j=V; j>=v[i]; j--){
            f[j] = max(f[j], f[j-v[i]] + w[i]); //f[j]表示体积为j时的价值
        }
    }
    printf("%d", f[V]);

    return 0;
}


2. 完全背包:

- 每个物品可以选择无数次

  • 有n件物品,每个物品无数件,第i个物品体积为vi,价格为ci现在有一个体积为V的背包,请你从n件物品里选出若干件放进背包里使得背包里的物品价值最大。
  • 我们用和0/1背包一样的状态,f[i][j]表示前i种物品放入一个容量为j的背包的最大价值,那我们应该用k表示当前容量下可以装第i种物品的件数,那么k的范围应该是0≤k≤V/v[i].
  • 状态转移方程为: f[i][j]=max(f[i-1][j],f[i-1][j-kv[i]]+kc[i])
  • 极端情况比如n个物品体积均为1,此时k=V,时间效率为O(n*V2)

模板代码:


  • 因为每种物品有无穷个,体积也是无穷的,则我们可以省略一维。推理如下:

  • 则最终化简后:

状态转移方程:f[j] = max(f[j], f[j-v[i]] + w[i]);


模板如下:

太神奇了,第二维倒序是0/1背包,正序是完全背包,我就问你们服不服!



3. 多重背包:

  • 有n种物品,每种物品bi件,第i个物品体积为vi,价格为ci.现在有一个体积为V的背包,请你从n件物品里选出若干件放进背包里使得背包里的物品价值最大。(每种物品可以选固定次数)
  • 数据范围:0<n<=100,0<V<=10000.物品和背包体积为正整数,保证所有物品价值为正,且价值和在int范围内
  • 这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有b[i]+1种策略:取0件,取1件…取b[i]件。令f[i][j]表示前i种物品放入一个容量为j的背包的最大权值,则有状态转移方程:f[i][j]=max(f[i-1][j-kv[i]]+kc[i]) 0<=k<=b[i]
  • 复杂度是O(V*Σb[i])。 显然时间效率有点低下,我们可以借鉴完全背包的二进制优化。





例:

样例输入:

 4 5
 1 2 3
 2 4 1
 3 4 3
 4 5 2

样例输出:

10

解:

#include<bits/stdc++.h>
using namespace std;

const int MAX = 10050;

int n, m;
int v[MAX], w[MAX], s[MAX];
int f[MAX];

int main(){

    scanf("%d%d",&n, &m);
    for(int i=1; i<=n; i++){
        scanf("%d%d%d",&v[i], &w[i], &s[i]);
    }

    for(int i=1; i<=n; i++){
        for(int j=1; j<=s[i]; j++){  //j遍历i物品使用次数
            for(int k=m; k>=v[i]; k--){
                f[k] = max(f[k], f[k-v[i]]+w[i]); //如0/1背包一样
            }
        }
    }
    
    printf("%d", f[m]);

    return 0;
}



4. 分组背包:

  • 每若干物品分为一组,每组中只能选一件物品。
  • 现有体积为V的背包,有n种物品,第i件物品重量为wi,价值为pi。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。
  • 每组物品要么一件不取,要么只取其中的一件,跟0/1背包很类似,0/1背包是对单个物品而言,而分组背包是对分组而言
  • 定义f[i][j]表示第i组物品在背包容量为j对第i组的第k个物品进行决策的最大价值,动态转移方程为: f[i][j]=max(f[i-1][j],f[i-1][j-w[g[i][k]]]+c[g[i][k]]





例:

样例输入:

 10 6 3
 2 1 1
 3 3 1
 4 8 2
 6 9 2
 2 8 3
 3 9 3

样例输出:

20

解:

#include<bits/stdc++.h>
using namespace std;

const int MAX = 1500;

int n, V, T;
int m[MAX], p[MAX], vp[MAX][MAX], g[MAX];	 
int f[MAX];
int main(){	

    int P;
    scanf("%d%d%d",&V, &n, &T);
    for(int i=1; i<=n; i++){
        scanf("%d%d%d",&m[i], &p[i], &P);
        g[P]++;  //g[P]表示P组中物品数量
        vp[P][g[P]] = i;  //eg.vp[1][2]表示第一组中第二个物品
    }

    for(int i=1; i<=10; i++){  //第一层按组依次遍历
        for(int j=V; j>=0; j--){
            for(int k=1; k<=g[i]; k++){  //该组中物品一次遍历求最大
                if(j >= m[vp[i][k]])
                    f[j] = max(f[j], f[ j-m[vp[i][k]] ]+p[vp[i][k]] );
            }
        }
    }
    printf("%d", f[V]);

return 0;
}


posted @ 2024-02-17 16:45  Aqr_Rn  阅读(18)  评论(0编辑  收藏  举报