背包问题(dp动态规划思路详解)

背包问题1---动态DP

题目:有n个重量和价值分别为wi,vi的物品。从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大的值。

输入:n = 4, (w, v)  = {(2, 3), (1, 2), (3, 4), (2, 2)},W = 5

输出:W = 5

思路:
首先我们采用递归的想法。定义一个函数 int rec(int i, int j)。这个函数的作用是求得挑选的最佳效果。调用的时候分别传入0,W。
然后我们就要开始递归了,采用分治策略。
我们从0个物品开始向后思考是否要选取这个物品
int rec(int i, int j){
	int res;
	if(i == n){如果这个物品已经超出范围了,因为i传入的时候是0,最后一个i应该是n-1 
	说明我们无法选择了:
	res = 0;
	}esle if(j < w[i]){
	如果当前这个物品的重量比我们还需要选的重量还要大,就选不了这个物品
	选不了我们就跳过它选下一个:
	res = rec(i + 1, j);
	//这里喃我们需要一种抽象思想,就是通过rec函数就能	出最佳的res。这个物品选不了了,我们从下一个	物品开始选出最佳答案。
	}else {
	可以选了,我们比较选它和不选它那个是更有价值的:
	res = max(rec(i + 1, j), rec(i + 1, j - w[i]) + v[i]);
	// rec(i + 1, j):同上,表示我们不选它,从下一个开始选出最佳解
	//rec(i + 1, j - w[i]) + v[i]:我们知道调用rec(i + 1, j - w[i])选了本物品后剩下还要选的重量在下一个开始的		大价值 + 本物品价值,也就是总的rec(i, j)的最大价值
	}
}

递归代码:

/*
输入:
4
2 3
1 2
3 4
2 2
5
输出:7
*/
#include
#include
using namespace std;
int n, W;
const int MAX_N = 100;
int w[MAX_N], v[MAX_N];
//从第i个物品开始挑选总重量小于j的部分
int rec(int i, int j){
    int res;
    if(i == n){
        //已经没有剩余的物品了
        res = 0;
    }else if(j < w[i]){
        //无法挑选这个物品(判断下一个)
        res = rec(i + 1, j);
    }else{
        //不挑选和挑选的两种情况分别尝试一下
        res = max(rec(i + 1, j), rec(i + 1, j - w[i]) + v[i]);
    }
    return res;//返回挑选了价值
}
void solve(){
    printf("%d\n", rec(0, W));
}
int main(){

    while(scanf("%d", &n) != EOF){
        for(int i = 0; i < n; i++){
            scanf("%d%d", &w[i], &v[i]);
        }
        scanf("%d", &W);
        solve();
    }
}

但是我们发现些时候递归是很重复的,所以我们定义一个数组dp来保存每次递归的结果,如果已经递归过,我们就直接返回结果

int dp[MAX_N + 1][MAX_W + 1];//记忆化数组

int rec(int i, int j){
    if(dp[i][j] >= 0){
        //已经计算过的话直接使用之前的结果
        return dp[i][j];
    }
    int res;
    if(i == n){
        res = 0;
    }else if(j < w[i]){
        res = rec(i + 1, j);
    }else{
        res = max(rec(i + 1, j), rec(i + 1, j - w[i]) + v[i]);
    }
    //将结果记录在数组中
    return dp[i][j] = res;
}
void solve(){
    //用-1表示尚未计算过,初始化整个数组
    //使用memset可以对1字节为单位进行填充。对1,0可进行高速初始化。不过无法初始化化为1
    memset(dp, -1, sizeof(dp));
    printf("%d\n", rec(0, W));
}

后来我们又发现明明数组就能搞定的为啥还要递归喃?所以直接用dp数组就可以解决问题了。

下面我们进行逆向循环规划:

int dp[MAX_N + 1][MAX_W + 1];//要都初始化成0

void solve(){
    for(int i = n - 1; i >= 0; i--){//我们从最后一个物品开始选(逆向)
        for(int j = 0; j <= W; j++){
            if(j < w[i]){//j表示可以选到的重量,w[i]是当前物品的重量
                dp[i][j] = dp[i + 1][j];
            }else{
                dp[i][j] = max(dp[i + 1][j], dp[i + 1][j - w[i]] + v[i]);
            }
        }
    }
    printf("%d\n", dp[0][W]);
}
下面我们进行正向循环规划:
void solve(){
	for(int i = 0; i < n; i++){
		for(int j = 0; j <= W; j++){
			if(j < w[i]){
				dp[i + 1][j] = dp[i][j];
			}else{
				dp[i + 1][j] = max(dp[i][j], dp[i][j - w[i]] + v[i]);
			}
		}
	}
	printf("%d\n", dp[n][W]);
}
其他的dp方式:
void solve(){
	for(int i = 0; i < n; i ++){
		for(int j = 0; j <=W; j++){
			dp[i + 1][j] = max(dp[i + 1][j], dp[i][j]);
			if(j + w[i] <= W){
				dp[i + 1][j + w[i]] = max(dp[i + 1][j + w[i]], dp[i][j] + v[i]);
			}
		}
	}
	printf("%d\n", dp[n][W]);
}


你可能感兴趣的:(OJ)