zoukankan      html  css  js  c++  java
  • 从记忆化搜索到动态规划

     1、要想理解动态规划,首先一定要理解记忆化搜索。

    以01背包问题为例:

    有 n 个重量和价值为 wi [i] ,v [i] 的物品,从这些物品中挑出重量不超过 t 的物品,求所有选择方案中价值总和的最大值。

    首先上的无记忆优化的递归搜索:

    #include<bits/stdc++.h>
    using namespace std;
    
    const int qq = 1e5 + 10;
    const int INF = 1e9 + 10;
    const int MOD = 1e9 + 7;
    
    int n,t;
    int wi[qq];
    int vi[qq];
    
    int rec(int i,int j)    // i,j含义:从第i个物品开始挑选总重小于j的部分
    {
        int res;
    
        if(i == n)          //递归的终结条件,每次一定都会走到i == n
            res = 0;
        else if (j < wi[i]) // 如果发现 j < wi[i] 即背包装不下第i个物品,就向下一个物品 i+1 递归
            res = rec(i+1,j);
        else                // 如果装的下,在此分叉出两种情况:1.不装这个东西,留着空间装价值vi更大的东西 2.装这个东西,背包的容量减小wi[i]
            res = max(rec(i+1,j),rec(i+1,j-wi[i])+vi[i]);
    
        return res;         // 运行到最后返回 res 让递归的上一层判断
    }
    
    int main()
    {
    
        cin >> n >> t;
    
        for(int i=0;i < n;i++)
            cin >> wi[i] >> vi[i];
    
        cout << rec(0,t) << endl;
    
        return 0;
    }

    在这种情况下,递归的缺点暴露,即大量重复的参数调用,因此我们可以做以下的记忆化搜索优化,只需要修改很小一部分。

    #include<bits/stdc++.h>
    using namespace std;
    
    const int qq = 1e5 + 10;
    const int INF = 1e9 + 10;
    const int MOD = 1e9 + 7;
    
    int n,t;
    int wi[qq];
    int vi[qq];
    int dp[qq][qq];
    
    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 < wi[i])
            res = rec(i+1,j);
            
        else
            res = max(rec(i+1,j),rec(i+1,j-wi[i])+vi[i]);
    
    
        return dp[i][j] = res; // 返回 res 的同时令dp[i][j] = res;
    }
    
    int main()
    {
        memset(dp,-1,sizeof(dp)); //dp初始化
    
        cin >> n >> t;
    
        for(int i=0;i < n;i++)
            cin >> wi[i] >> vi[i];
    
        cout << rec(0,t) << endl;
    
        return 0;
    }

    只需要这么一个小小的优化,复杂度缩小到O(NT)

    2.从记忆化搜索到动态规划

    #include<bits/stdc++.h>
    using namespace std;
    
    const int qq = 1e5 + 10;
    const int INF = 1e9 + 10;
    const int MOD = 1e9 + 7;
    
    int n,t;
    int wi[qq];
    int vi[qq];
    int dp[qq][qq];
    
    int main()
    {
        memset(dp,0,sizeof(dp));
    
        cin >> n >> t;
    
        for(int i=0;i < n;i++)
            cin >> wi[i] >> vi[i];
        
        for(int i=n-1;i >= 0;i--)   //为什么 i 从 n-1 开始? 想一想递归都是一直运行到最后才开始计算的。
        {
            for(int j=0;j <= t;j++) // j 从 0 到 t 遍历 , 但我有个疑惑:记忆化搜索是每次减wi[i],是不是会比动规 j 遍历的次数少?
            {
                if(wi[i] > j)
                    dp[i][j] = dp[i+1][j];
                else
                    dp[i][j] = max(dp[i+1][j],dp[i+1][j-wi[i]]+vi[i]);
            }
        }
        
        cout << dp[0][n] << endl;
        
        return 0;
    }

    这个样子的动态规划是完全按照记忆化搜索演变过来的

    3.动态规划的各种变形

    很多时候你看到的别人的解法都是很简略、美观的代码,其实这些都是基于熟练掌握的基础上浓缩而成的,基本上是无法从中理解的。

    此为正序递推式:

    for(int i=0;i < n;i++)
        {
            for(int j=0;i <= t;j++)
            {
                if(j < wi[i])
                    dp[i+1][j] = dp[i][j];
                else
                    dp[i+1][j] = max(dp[i][j],dp[i][j-wi[i]]+vi[i]);
            }
        }

    一维DP

    for(int i=0;i < n;i++)
        {
            for(int j=t;j >= wi[i];j--)
            {
                dp[j] = max(dp[j],dp[j-wi[i]]+vi[i]);
            }
        }

    动态规划是很抽象的,之前我很总是习惯要搞清楚 dp[i][j] 是怎样在数组中演变过来的,后来觉得思考不过来。只要清楚dp[i][j]是什么意思就好了。 

  • 相关阅读:
    树链剖分 (模板) 洛谷3384
    ST表 (模板) 洛谷3865
    IOI 2005 River (洛谷 3354)
    IOI 2005 River (洛谷 3354)
    poj1094 Sorting It All Out
    poj1094 Sorting It All Out
    spfa(模板)
    HAOI 2006 受欢迎的牛 (洛谷2341)
    HAOI 2006 受欢迎的牛 (洛谷2341)
    洛谷1850(NOIp2016) 换教室——期望dp
  • 原文地址:https://www.cnblogs.com/cunyusup/p/8387787.html
Copyright © 2011-2022 走看看