动态规划入门——背包问题

01背包问题之1

问题描述

有n个重量和价值分别为wi,vi的物品。从这些物品中选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。在这里,每个物品只能选一件。

限制条件:

1≤n≤100

1≤wi,vi≤100

1≤W≤10000

样例

输入

n = 4

(w, v) = {(2,3),(1,2),(3,4),(2,2)}

W = 5

输出

7(选择0、1、3号物品)

样例代码

暴力法(复杂度O(2n))

#include<cstdio>
#include<algorithm>
using namespace std;
int n;
int w[105], v[105];
int rec(int i, int j)    //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]);
    //rec(i+1, j)为不选该物品,rec(i+1, j - w[i])+v[i])为选择该物品
    return res;
}
int main()
{
    scanf("%d", &n);
    int i;
    for(i = 0; i < n; i++)
        scanf("%d%d", &w[i], &v[i]);
    int W;
    scanf("%d", &W);
    printf("%d\n", rec(0, W));
    return 0;
}

上述算法复杂度太高,于是用了个记忆化数组,如果在递归时该内容已经在之前出现过,直接返回该值

此时复杂度为O(nW)

#include<cstdio>
#include<algorithm>
using namespace std;
int n;
int w[105], v[105];
int dp[105][105];
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;
}
int main()
{
    scanf("%d", &n);
    int i;
    for(i = 0; i < n; i++)
        scanf("%d%d", &w[i], &v[i]);
    int W;
    scanf("%d", &W);
    rec(0, W);
    printf("%d\n", rec(0, W));
    return 0;
}

这是用递归的方式写的,再来看一下用循环的方式写的01背包

复杂度同上

#include<cstdio>
#include<algorithm>
using namespace std;
int n;
int w[105], v[105];
int dp[105][105];
int rec(int W)
{
    int i, j;
    for(i = 0; i <n; i++)
    {
        for(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]);
        }
    }
    return 0;
}
int main()
{
    scanf("%d", &n);
    int i;
    for(i = 0; i < n; i++)
        scanf("%d%d", &w[i], &v[i]);
    int W;
    scanf("%d", &W);
    rec(W);
    printf("%d\n", dp[n][W]);
    return 0;
}

完全背包问题

问题描述

有n种重量和价值分别为wi和vi的物品。从这些物品中挑选总重量不超过W的物品,求出挑选物品价值总和的最大值。在这里每件物品可以挑选任意多件。

1≤n≤100

1≤wi,vi≤100

1≤W≤10000

样例

输入

n = 3

(w, v) = {(2,3),(3,4),(4,5)}

W = 7

输出

10(0号物品选1个,2号物品选2个)

样例代码

由可选任意多件可得下方代码

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

显而易见,该代码复杂度为O(nW2),并不是最优解

#include<cstdio>
#include<algorithm>
using namespace std;
int n;
int w[105], v[105];
int dp[105][105];
int rec(int W)
{
    int i, j;
    for(i = 0; i <n; i++)
    {
        for(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+1][j-w[i]] + v[i]);  
//完全背包为i } } return 0; } int main() { scanf("%d", &n); int i; for(i = 0; i < n; i++) scanf("%d%d", &w[i], &v[i]); int W; scanf("%d", &W); rec(W); printf("%d\n", dp[n][W]); return 0; }

此复杂度为(nW),对比一下可以发现,与01背包问题代码略有相像

此外,01背包问题和完全背包问题也可以通过不断重复利用一个数组来实现(小心使用,易出bug)

01背包

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

完全背包

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

01背包问题之2

问题描述

有n个重量和价值分别为wi,vi的物品。从这些物品中选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。在这里,每个物品只能选一件。

限制条件:

1≤n≤100

1≤wi≤107

1≤vi≤100

1≤W≤109

样例

输入

n = 4

(w, v) = {(2,3),(1,2),(3,4),(2,2)}

W = 5

输出

7(选择0、1、3号物品)

分析

显而易见,与01背包问题之1相比,数值变大了,也就不能用O(nW)的算法了,相对于重量而言,价值的数值比较小,所以尝试一下改变dp的对象,用dp针对不同价值的最小重量。

代码如下

int dp[MAX_N+1][MAX_V+1];
int rec(int W)
{
    fill(dp[0], dp[0]+MAX_N*MAX_V + 1, INF);
    int i, j;
    dp[0][0] = 0;
    for(i = 0; i < n; i++)
    {
        for(j = 0; j < MAX_N*MAX_V; j++)
        {
            if(j < v[i])
                dp[i+1][j] = dp[i][j];
            else
                dp[i+1][j] = min(dp[i][j], dp[i][j-v[i]] + w[i]);
        }
    }
    int res = 0;
    for(i = 0; i <= MAX_N*MAX_V; i++)
        if(dp[n][i] <= W)
        res = i;
    printf("%d\n", res);
    return 0;
}

 

posted @ 2019-04-02 21:49  really41  阅读(198)  评论(0)    收藏  举报