01背包和完全背包--动态规划(1)

今天开始看动规。

从最基本的背包问题开始:首先是基本的01背包。

简单的来说就是一个问题 可以分解成相似的若干个子问题 然后通过记录子问题的解来一步一步推出最终的最优解。

这样做的好处就是:可以通过记录状态值 重复利用(记忆结果)来减少传统递归的次数 从而优化时间复杂度。

 

动规中 最重要的莫过于列出对应的 状态转移方程。

 

假设有n件物品,用v[n]储存它们的价值,w[n]储存他们的重量。要放进一个总容量为total的背包里,最大价值是多少?

 

用一个数组dp[n][total]来储存每一步的状态。dp[i][j]表示的意义是将i个物品放进容量为j的背包里所能达到的最大价值。

 

那么状态转移方程就是dp[i][j] = max{dp[i-1][j],dp[i-1][j-w[i]]+v[i]}

 

这个打表过程也很好理解了,i从1到n,j从0到total,两个循环打表 最后输出dp[n][w]就可以了

 

空间优化:其实仔细观察就可以发现并不需要这么多的数组。在推dp[i][]时只需要dp[i-1][]这一层而已。也就是实际只需要两层。

再进一步。计算dp[i][j]只需要dp[i-1][j]和dp[i-1][j-w[i]]两个值。假设我们从后往前打表的话,就可以利用上一组的数据了。

你可以理解成 从后往前打 就将整个数组分为两个部分:前面是没有打的 隶属于dp[i-1]这一层。后面是打过的 隶属于dp[i]这一层。后面的计算需要利用前面的变量 这样的话,只需要一个一维数组就解决掉这个问题了。

 

改良后的状态转移方程为dp[i] = {dp[i],dp[i-w[i]]+v[i]}; 理解一下

 

下面 进一步 来看完全背包问题。

 

完全背包比起01背包问题 唯一的不同之处就是:每种物品都可以有无限个。

 

这样子的话 传统的状态转移方程就变为:dp[i][j] = max{dp[i-1][j],d[i-1][j-k*w[i]]+k*v[i]};

当背包容量很大的时候会变得复杂

 

为了便于理解我们可以把这个转换成01背包。每个物品最多只能放total/w[i]个。因此内部添加一个循环即可。

 

但是时间复杂度并没有降低。这时候我们回到之前空间优化过的01背包转移方程。在那里为了保证转移过来的状态来自dp[i-1],我们是从后往前打表的

如果从前往后打表会发生什么?很简单 每个状态都会被重复计算,这正是完全背包的思路。

 

例题 HDU1248 巫妖王的工资

用优化过的dp方法 代码如下:

# include<iostream>
# include<algorithm>

using namespace std;

int dp[10001] = {0};

int main()
{
    int T, N;

    cin >> T;

    int val[3] = { 150, 200, 350 }, w[3] = { 150, 200, 350 };

    for (int i = 0; i < T; i++)
    {
        cin >> N;

        for (int j = 0; j < 3; j++)
        {
            for (int k = 1; k <= N; k++)
            {
                if (k >= w[i])
                {
                    dp[k] = max(dp[k-1],dp[k-w[i]]+val[i]);
                }
            }

        }

        cout << N - dp[N] << endl;
    }

    return 0;
}

细节部分一定要多想想 尽量搞明白。

这周继续动规。很多事情都来不及做了。之前浪费掉的时间无论如何也要补救回来

 

posted @ 2015-12-28 23:20  DigitalHermit  阅读(399)  评论(0编辑  收藏  举报