动态规划-背包问题1

一、背包问题

“背包”是程序设计和信息学竞赛中的一类重要问题。背包问题种类繁多,其中最简单的就是“0-1背包”。青和优化策略。背包问题的求解涉及计算机算法的灵活应用:

1.背包问题

2.完全背包问题

3.多重背包问题

4.混合三种背包问题

5.二维费用的背包问题

6.分组的背包问题

二、例题讲解-0-1背包问题

【问题描述】

有N件物品和一个容量为V的背包。放入第i件物品耗费的空间是Ci,得到的价值是Wi。求解在不超过容量的前提下,将哪些物品装入背包可使价值总和最大。

【输入格式】

第1行两个正整数,分别表示N和V,中间用一个空格隔开。

第2行N个正整数,表示Ci,中间用一个空格隔开。

第3行N个正整数,表示 Wi,中间用一个空格隔开,

其中:1≤N<100,1≤V≤1e5,1≤Ci<1000,1≤Wi≤1000。

【输出格式】

一行一个正整数,表示最大的价值总和.

【输入样例】

4 20

8 9 5 2

5 6 7 3

【输出样例】

16

【问题分析】

  1. 定义状态F[i][v],表示前i件物品放入一个容量为v的背包可以获得的最大价值,则状态转移方程为F[i][v] = max{F[i-1][v], F[i-1, v-Ci] + Wi}

可以这样理解:

依次考虑每一件物品,对于第i件物要么不放、要么放,对应计算机中的0或1,所以是“0-1背包”,那么就,可以将一个规模为i的问是

转化为一个只和前i-1件物品相关的子问题(规模为i-1)。如果不放第i件物品,问题就转化为“前i-1件物品放入容量为v的背包”。如果不放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-Ci的背包”。此时能获得的最大价值就是F[i-1,v-Ci]再加上通过放入第i件物品获得的价值。

状态:F[i,v]表示用体积为v的背包装前i个物品能获得的最大价值。

考虑第i种物品装或不装进行状态转移:

1.装:f[i-1,v-C[i]]+W[i](必须满足v≥C[i])

2.不装:f[i - 1, v]

两种情况取较大值。

条件转移方程为:

答案为f[N,V],时间复杂度为O(NV)

我们发现,其中第i行的值只与第i-1行有关系。很自然的想到用滚动数组来优化空间复杂度到O(2*N);根据这个思路写成代码如下:

#include<bits/stdc++.h>

using namespace std;

int N, V;

int F[101][10000];

int C[101], W[101];



int main(){

    cin >> N >> V;        //物品数量N和背包容量V

    for(int i = 1; i <= N; i++){

        cin >> C[i];        //第i件商品空间

    }

    for(int i = 1; i <= N; i++){

        cin >> W[i];    //第i件商品价值

    }

    

    for(int i = 1; i <= N; i++){        //F[i][v]表示前i件物品,总重量不超过v的最优价值

        for(int v = 0; v <= V; v++){

            if(v >= C[i]){

                F[i][v] = max(F[i - 1][v], F[i - 1][v - C[i]] + W[i]);

            }else{

                F[i][v] = F[i - 1][v];

            }

        }

    }

    cout << F[N][V];        //F[N][V]为最优解

    

    return 0;

}

我们再仔细分析第一个程序,发现F[i, v]是由F[i-1, v]和F[i-1, v-Ci]两个子问题逆推而来,能否保证在求F[i, v]时(也即在第二个程序的第i次主循环中推F[v]时)能够取用F[i-1, v]和F[i-1, v-Ci]的值呢?事实上,这要求我们在每次主循环中以v = V..0的递减顺序计算F[v],这样就能保证在推F[v]时F[v-Ci]保存的是状态F[i-1, v-Ci]的值。

程序中的 F[v] = max{F[v], F[v-Ci]+Wi} 就对应于原来的转移方程,因为现在的 F[v-Ci] 就相当于原来的 F[i-1, v-Ci]。

以上代码还可以做一个“常数优化”,将第二重循环的下限 Ci 改为:

max{V-Σi=1^N Wi, Ci}

由此可以得到01背包的公式:

for i=1..N

for v=V..0

f[v]=max{f[v],f[v-w[i]]+c[i]};

代码如下:

#include<bits/stdc++.h>

using namespace std;

int N, V, c[110], w[110], F[1000000];    //F[V]当v容量时获得的最大价值

int main(){

    cin >> N >> V;

    for(int i = 1; i <= N; i++){

        cin >> c[i];

    }

    for(int i = 1; i <= N; i++){

        cin >> w[i];

    }

    

    //01背包算法

    for(int i = 1; i <= N; i++){

        for(int v = V; v >= c[i]; v--){

            F[v] = max(F[v], F[v - c[i]] + w[i]);

        }

    }

    cout << F[V];

    return 0;

}
三、例题讲解-01背包+输出方案

题目描述

一个旅行者有一个最多能装 M 公斤的背包,现在有 ��n 件物品,它们的重量分别1,2,…,W1,W2,…,Wn,它们的价值分别为1,2,…,C1,C2,…,Cn,求旅行者能获得最大总价值。

输入

第一行:两个整数M (背包容量,M≤200)和N (物品数量N≤30);

第2..N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。

输出

两行,第一行一个数,表示最大总价值。第二行为具体方案,以空格分开。

样例输入1         

8 4

2  3

3  4

4  5

5  6

样例输出1        

10

3 5

【分析】

这道题和上一道题基本一样。

按照样例来分析一下:

// 输出方案

void findWhat(int i, int v) {

    if (i > 0) {

        if (f[i][v] == f[i - 1][v]) { // 没有放

            item[i] = 0;

            findWhat(i - 1, v);

        } else if (v - w[i] >= 0 && f[i][v] == f[i - 1][v - w[i]] + c[i]) { // 放了

            item[i] = 1;

            findWhat(i - 1, v - w[i]);

        }

    }

}

所以总的代码如下:

#include<bits/stdc++.h>

using namespace std;

#define M 10000

int N,V,C[M],W[M];

int f[101][M];    //f[i][j]第i见物品v重量时的最大价值

bool item[M];



// 输出方案

void findWhat(int i, int v) {

    if (i > 0) {

        if (f[i][v] == f[i - 1][v]) { // 没有放

            item[i] = 0;

            findWhat(i - 1, v);

        } else if (v - C[i] >= 0 && f[i][v] == f[i - 1][v - C[i]] + W[i]) { // 放了

            item[i] = 1;

            findWhat(i - 1, v - C[i]);

        }

    }

}

int main(){

    cin >> V >> N;

    for(int i = 1; i <= N; i++){

    cin >> C[i];

    cin >> W[i];

}

    for(int i = 1; i <=N; i++){

        for(int v = 0; v <= V; v++){        //01背包

            if(v >= C[i]){

                f[i][v] = max(f[i - 1][v], f[i - 1][v - C[i]] + W[i]);

            }else{

                f[i][v] = f[i - 1][v];

            }

        }

    }

    

    cout << f[N][V] << endl;

    findWhat(N, V);    //找出具体方案

    for(int i = 1; i <= N; i++){

        if(item[i]){

            cout << C[i] << ' ';

        }

    }

    

    return 0;

}
四、完全背包问题

题目描述

设有 n 种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为 M,今从 n 种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于 M,而价值的和为最大。

输入

第一行:两个整数,M (背包容量M≤200)和 N (物品数量N≤30);

第2..N+1 行:每行二个整数 Wi、Ci,表示每个物品的重量和价值。

输出

仅一行,一个数,表示最大总价值。

样例输入1         

10 4

2 1

3 3

4 5

7 9

样例输出1         

max=12

【完全背包问题题目分析】

基本思路:

这个问题非常类似于01背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略己并非取或不取两种,而是有取0件、取1件、取2件...等很多种。

如果咱们按照解01背包时的思路,用f[i][v]表示前i种物品恰好放入一个容量为v的背包的最大权值。仍然可以按照每种物品不同的策略出状态转移方程,像这样

将01背包问题的基本思路加以改进,得到了这样一个清晰的方法。这说明01背包问题的方程的确是很重要,可以推及其它类型的背包问题。

这个算法使用一维数组,先看伪代码:

你会发现,这个伪代码与01背包问题的伪代码只有v的循环次序不同而已。 为什么这样一改就可行呢?

首先想想为什么01背包问题中要按照v=V..0的逆序来循环。这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-w[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无己经选入第i件物品的子结果f[i-1][v-w[i]]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已经选入第i种物品的子结果f[i][v-w[i]],所以就可以并且必须采用v=0..V的顺序循环。这就是这个简单的程序为何成立的道理。

这个算法也可以以另外的思路得出,例如:基本思路中的状态转移方程可以等价的变形称为这种形式:

(求解方案数)

将这个方程用一维数组实现,便得到了上面的伪代码。

【题目解法一】

设f[i][y]表示前i件物品,总重量不超过v的最优价值,

F[n][m]即为最优解。

#include<cstdio>

#include<bits/stdc++.h>

using namespace std;

const int maxm = 201, maxn = 31;

int m, n;

int w[maxn], c[maxn];

int f[maxn][maxm];

int main(){

    cin >> m >> n;    //背包容量m和物品数量n

    for(int i = 1; i <= n; i++){

        cin >> w[i] >> c[i];    //每个物品的重量和价值

    }

    for(int i = 1; i <= n; i++){    //f[i][v]表示前i件物品,总重量不超过v的最优价值

        for(int v = 1; v <= m; v++){

            if(v < w[i]){

                f[i][v] = f[i - 1][v];

            }else{

                f[i][v] = max(f[i - 1][v], f[i][v - w[i]] + c[i]);

            }

        }

    }

    printf("max=%d", f[n][m]);    //f[n][m]为最优解

    return 0;

    

}

【方法二】

本问题的数学模型如下:

求f[v]表示重量不超过v公斤的最大价值,即

#include<bits/stdc++.h>

using namespace std;

const int maxm = 2001, maxn = 31;

int n, m, v, i;

int c[maxn], w[maxn], f[maxm];

int main(){

    cin >> m >> n;        //背包容量m和物品数量N

    for(int i = 1; i <= n; i++){

        cin >> w[i] >> c[i];

    }

    for(int i = 1; i <= n; i++){

        for(v = w[i]; v <= m; v++){    //设f[v]表示重量不超过v公斤的最大价值

            if(f[v - w[i]] + c[i] > f[v]){

                f[v] = f[v - w[i]] + c[i];

            }

        }

    }

    printf("max=%d\n", f[m]);    //f[m]为最优解

    return 0;

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值