• DP的初级问题——01包、最长公共子序列、完全背包、01包value、多重部分和、最长上升子序列、划分数问题、多重集组合数


      当初学者最开始学习 dp 的时候往往接触的是一大堆的 背包 dp 问题, 那么我们在这里就不妨讨论一下常见的几种背包的 dp 问题;

       初级的时候背包 dp 就完全相当于BFS DFS 进行搜索之后的记忆化查找。

      背包问题 一 、   0 ~ 1 背包问题

        实现一、  return max ( rec ( i + 1, j ) , rec ( i + 1, j - cost[ i ]) + value[ i ]);      //对第 i  件物品的选或者不选

        记忆化、      这个是由于实现一产生的dp数组 : dp[ i ] [ j ] : 首先注意这个 i 表示的是第 i 件物品, j 表示的是剩余的价值, 那么这个整体的 dp 表示的就是 i 件物品到 最后一个物品 n - 1 他能够产生的最大价值;

            同样我们利用那个递归关系,我们可以知道

          dp[ i ] [ j ] = max ( dp[ i + 1] [ j ] ,  dp[ i + 1] [ j - w[ i ] ] + value [ i ]) ; 

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <cstdlib>
     4 #include <cstring>
     5 #include <cmath>
     6 #include <cctype>
     7 #include <algorithm>
     8 #include <vector>
     9 #include <queue>
    10 #include <list>
    11 #include <map>
    12 #include <stack>
    13 #include <set>
    14 #include <string>
    15 
    16 using namespace std;
    17 const int MAX_N = 1010;
    18 const int MAX_W = 1010;
    19 int dp[MAX_N][MAX_W];
    20 int N, W;
    21 int w[MAX_N], value[MAX_N];
    22 int main()
    23 {
    24     cin>>N>>W;
    25     for(int i = 0; i < N; i++)  cin>>w[i]>>value[i];
    26     dp[N][0] = 0;
    27     fill(dp[N], dp[N] + W, 0);
    28     for(int i = N - 1; i >= 0; i--){
    29         for(int j = 0; j <= W; j++){
    30             if(j < w[i])
    31                 dp[i][j] = dp[i+1][j];
    32             else
    33                 dp[i][j] = max(dp[i+1][j], dp[i+1][j-w[i]] + value[i]);
    34         }
    35     }
    36     return 0;
    37 }
    View Code

      如果你觉着这样逆着推导有点不舒服的话, 我们可以简单的对dp 数组换一下定义, 我们从原来的 dp [i] [j]  使用由 i 到最后; 同样dp[ i + 1 ] [ j ]我们可以设定为 从 0 到 i 这  i + 1 个物品, 但是你需要注意了;

      

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <cstdlib>
     4 #include <cstring>
     5 #include <cmath>
     6 #include <cctype>
     7 #include <algorithm>
     8 #include <vector>
     9 #include <queue>
    10 #include <list>
    11 #include <map>
    12 #include <stack>
    13 #include <set>
    14 #include <string>
    15 
    16 using namespace std;
    17 const int MAX_N = 1010;
    18 const int MAX_W = 1010;
    19 int dp[MAX_N][MAX_W];
    20 int N, W;
    21 int w[MAX_N], value[MAX_N];
    22 int main()
    23 {
    24     cin>>N>>W;
    25     for(int i = 0; i < N; i++)  cin>>w[i]>>value[i];
    26     fill(dp[0], dp[0] + W, 0);
    27     for(int i = 0; i < N; i++){
    28         for(int j = 0; j <= W; j++){
    29             if(j < w[i])
    30                 dp[i+1][j] = dp[i][j];
    31             else
    32                 dp[i+1][j] = max(dp[i][j], dp[i][j-w[i]] + value[i]);
    33         }
    34     }
    35    printf("THE RESULT : %d
    ", dp[N][W]);
    36     return 0;
    37 }
    View Code

       最长公共子序列问题:

      那么我们完全可以把看看成追赶问题, 而且联想 dp 背包问题的 i + 1而不是 i 的巧妙设定, 这样防止了数组下标的越界!

        dp[ i + 1] [ j + 1] : 这个指的是 s0[ i ] 与 s0[ j ]   到这两个字符串的这两个元素的时候, 他目前的最长公共子序列长度的问题, i 是表示长度为 i ; j 表示长度为 j ; 分别表示了两个子序列的长度。

      那我们利用追赶问题的方法进行寻找上下级别的关系:

      dp[ i + 1] [ j + 1] = dp[ i ] [ j ] + 1; 这个是当且仅当 s0[ i ] = s1[ j ] ;

      当不满足这个等式的时候

      dp [ i + 1] [ j + 1] = max ( dp[ i + 1] [ j ],  dp[ i ] [ j +1])   //这个就是因为最后两个不相等, 不可能同时使用, 所以说我们删去一个 , 还可以减小问题的规模!

      

     1 #include <iostream>
     2 #include <cstring>
     3 #include <bits/stdc++.h>
     4 #include <algorithm>
     5 #include <cstdio>
     6 #include <cstdlib>
     7 #include <string>
     8 #include <cctype>
     9 #include <cmath>
    10 using namespace std;
    11 const int MAX_S = 1010;
    12 char s0[MAX_S], s1[MAX_S];
    13 int len1, len0;
    14 int dp[MAX_S][MAX_S];
    15 int main()
    16 {
    17     cin>>s0>>s1;
    18     len0 = strlen(s0);
    19     len1 = strlen(s1);
    20     fill(dp[0], dp[0] + MAX_S, 0);
    21     for(int i = 0; i < len0; i++){
    22         for(int j = 0; j < len1; j++){
    23             if(s0[i] == s1[j]){
    24                 dp[i+1][j+1] = dp[i][j] + 1;
    25             }
    26             else{
    27                 dp[i+1][j+1] = max(dp[i+1][j], dp[i][j+1]);
    28             }
    29         }
    30     }
    31     printf("THE ANSWER IS THAT : %d
    ", dp[len0][len1]);
    32     return 0;
    33 }
    34 
    35 /*
    36 abcd
    37 becd
    38 
    39 
    40 */
    41  
    View Code

    终于到了兄弟们最喜欢的完全背包问题了 :

        那么对于这个类似于 0 1背包, 但是数量无限的情况下, 我们如何进行递归呢?

        最简单的、 最容易想的就是仿照我们的0 1 背包进行求解

        他是由第 i 个物品买还是不买,转换成了买多少个!!

        为了保持正向进行, 我们的 dp [ i + 1] [ j ]  表示的是在处理第 i 件产品, 结果是 0 - i 种物品合理选择后的最大价值量 ! 

      因此我们有 dp[ i + 1] [ j ] =  max(dp [ i ] [ j - k * w[ i ] ] + k * value[ i ] ) 注意这个是要保证下标索引是 大于零 的, 而且我们的 k 是自然数 ;

      但是不得不说这样的算法复杂度太高, 那么我们怎么进一步化简??  

      dp[ i + 1] [ j ] = max(dp [ i ] [ j ], dp[ i + 1] [ j - w[ i ]] + value[ i ]);

      注意了, 调用这个式子的时候一定要注意, 这个下标索引大于零, 而且初始化

       dp[ i + 1] [ j ] =  max(dp [ i ] [ j - k * w[ i ] ] + k * value[ i ] )  k >= 0;

            = max(dp[ i ] [ j ] , dp [ i ] [ j - k * w[ i ] ] + k * value[ i ] )  k >= 1;

            = max(dp[ i ] [ j ] , dp [ i ] [ j  - w[ i ] - k * w[ i ] ] + k * value[ i ]  + value[ i ])  k >= 0;

            = max(dp[ i ] [ j ] , dp [ i  + 1 ] [ j  - w[ i ] ]  + value[ i ]);  

      那么这样的证明就是好起来了。

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <string>
    #include <vector>
    #include <list>
    #include <map>
    #include <set>
    #include <stack>
    #include <cctype>
    #include <algorithm>
    #include <cmath>
    #include <queue>
    
    using namespace std;
    const int MAX = 105;
    int dp[MAX][MAX];
    int w[MAX], v[MAX];
    int W, N;
    
    //这个是对完全背包问题的解决
    //分为了两种解法
    int main()
    {
        int N;  cin>>N>>W;
        for(int i = 0; i < N; i++){
            scanf("%d%d", w + i, v + i);
        }
        memset(dp, 0, sizeof(dp));  //小小的初始化一手
        //不节省空间的写法
        for(int i = 0; i < N; i++){
            for(int j = 0; j <= W; j++){
                if(j-w[i] >= 0)
                    dp[i+1][j] = max(dp[i][j], dp[i+1][j-w[i]]+v[i]);
                else
                    dp[i+1][j] = dp[i][j];
            }
        }
        printf("THE FIRST RESULT IS %d
    ", dp[N][W]);
    
        //节省空间的写法
        memset(dp, 0, sizeof(dp));  //再次进行初始化
        for(int i = 0; i < N; i++){
            for(int j = 0; j <= W; j++){
                if(j-w[i] >= 0)
                    dp[0][j] = max(dp[0][j], dp[0][j-w[i]]+v[i]);
                else
                    dp[0][j] = dp[0][j];
            }
        }
        printf("THE SECOND RESULT IS %d
    ", dp[0][W]);
    
        return 0;
    }
    /*
    3 7
    3 4
    4 5
    2 3
    
    
    THE RESULT SHOULD BE 10
    
    */
    View Code

     0-1 背包问题二

         注意初始化时候的灵魂加一

         这个是适用于NV 复杂度, 还有一个普通的 NW 复杂度, 在下面的代码之中都有着提及

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <string>
    #include <vector>
    #include <list>
    #include <map>
    #include <set>
    #include <stack>
    #include <cctype>
    #include <algorithm>
    #include <cmath>
    #include <queue>
    
    using namespace std;
    const int MAX = 105;
    const int MAX_V = 100;
    const int INF = 1e6;
    int dp[MAX+1][MAX+1];
    int w[MAX], v[MAX];
    int W, N;
    
    
    int main()
    {
        int N;  cin>>N>>W;
        for(int i = 0; i < N; i++){
            scanf("%d%d", w + i, v + i);
        }
        memset(dp, 0, sizeof(dp));  //小小的初始化一手
        //0 1 背包的多种解法, 直接暴力 NW, 后者是进一步优化后的节省空间的一维数组的直接求解,又或者是NV
    
        //FIRST 直接暴力 dp NW      dp[i+1][j] 前 i 个物品的重量不超过 J 的最大价值
        for(int i = 0; i < N; i++){
            for(int j = 0; j <= W; j++){
                if(j >= w[i])
                    dp[i+1][j] = max(dp[i][j], dp[i][j - w[i]] + v[i]); //买或者不买
                else
                    dp[i+1][j] = dp[i][j];
            }
        }
        printf("FIRST RESULT IS %d
    ", dp[N][W]);
    
        //SECOND 多次利用了同一个一维数组, 复杂度没有变,但是在所占存储大大优化
        memset(dp, 0, sizeof(dp));
        for(int i = 0; i < N; i++){
            for(int j = W; j >=0 ; j--){
                if(j >= w[i])
                    dp[0][j] = max(dp[0][j], dp[0][j - w[i]] + v[i]); //买或者不买
                else
                    dp[0][j] = dp[0][j];
            }
        }
        printf("SECOND RESULT IS %d
    ", dp[0][W]);
    
    
    
        //THIRD O(NV)这个是需要看你的数据要求然后进行选择
        // dp[i+1][j] 表示的是, 对前 I 个物品进行选择,, 然后当前 J 价值的最小重量
        for(int i = 0; i < N; i++)  fill(dp[i], dp[i] + MAX_V + 1, INF), dp[i][0] = 0; //进行小小的初始化
        for(int i = 0; i < N; i++){
            for(int j = 0; j <= MAX_V; j++){
                if(j - v[i] >= 0)
                    dp[i+1][j] = min(dp[i][j], dp[i][j - v[i]] + w[i]); //当前获取价值所需要的最小重量
                else    dp[i+1][j] = dp[i][j];
            }
        }
        int res = 0;
        for(int i = 0; i <= MAX_V; i++){
            if(dp[N][i] <= W){
                res = i;    //我们需要找的是重量要求条件之内的最大价值
            }
        }
        printf("THE THIRD RESULT : %d
    ", res);
    
    
        //FOURTH O(NV)这个是需要看你的数据要求然后进行选择
        // dp[i+1][j] 表示的是, 对前 I 个物品进行选择,, 然后当前 J 价值的最小重量
        //这个是教科书上面最简短的代码
        fill(dp[0], dp[0] + MAX_V + 1, INF);  dp[0][0] = 0;
        for(int i = 0; i < N; i++){
            for(int j = 0; j <= MAX_V; j++){
                if(j - v[i] >= 0)
                    dp[i+1][j] = min(dp[i][j], dp[i][j - v[i]] + w[i]); //当前获取价值所需要的最小重量
                else    dp[i+1][j] = dp[i][j];
            }
        }
        res = 0;
        for(int i = 0; i <= MAX_V; i++){
            if(dp[N][i] <= W){
                res = i;    //我们需要找的是重量要求条件之内的最大价值
            }
        }
        printf("THE FOURTH RESULT : %d
    ", res);
        return 0;
    }
    /*
    4 5
    2 3
    1 2
    3 4
    2 2
    
    
    THE RESULT SHOULD BE 7
    
    */
    View Code

    多重部分和问题

    最长上升子序列问题    

     最简单的第一种算法, 就是把它抽象为追赶问题, 而且

      dp [ j ] = max ( dp [ j ] , dp [ i ] + 1 );    i < j 而且 a [ i ] < a [ j ];      有些问题, 我们需要的是最长下降子序列的问题 

      代码

     1 /*
     2     将本题目抽象出来居然是 最长上升子序列问题
     3 */
     4 #include <iostream>
     5 #include <cstdio>
     6 #include <cstdlib>
     7 #include <cstring>
     8 #include <cmath>
     9 #include <cctype>
    10 #include <algorithm>
    11 #include <vector>
    12 #include <queue>
    13 #include <list>
    14 #include <map>
    15 #include <stack>
    16 #include <set>
    17 #include <string>
    18 
    19 using namespace std;
    20 const int MAX_N = 4e4+10;
    21 int a[MAX_N];
    22 int dp[MAX_N];
    23 
    24 int main()
    25 {
    26     int T;  cin>>T;
    27     while(T--){
    28         int n;  cin>>n;
    29         for(int i = 0; i < n; i++){
    30             scanf("%d", &a[i]);
    31         }
    32         memset(dp, 0, sizeof(dp));
    33         int res = 0;
    34         dp[0] = 1;
    35         for(int i = 1; i < n; i++){
    36             dp[i] = 1;
    37             for(int j = 0; j < i; j++){
    38                 if(a[i] > a[j]){
    39                     dp[i] = max(dp[i], dp[j] + 1);
    40                     res = max(res, dp[i]);
    41                 }
    42             }
    43         }
    44         printf("THE RESULT :
    ");
    45         printf("%d
    ", res);
    46     }
    47     return 0;
    48 }
    View Code

      下一个dp [i] 就更牛逼了, 甚至可以使用二分查找; 真的是好用!!

      (注意下标存取的是长度, 然后dp [ i ] 存储是传说当中的当前长度的最小尾部元素)

      lower_bound : ai >= k  的最小指针

      upper_bound : ai >k  的最小指针 

     1 /*
     2     将本题目抽象出来居然是 最长上升子序列问题
     3 */
     4 #include <iostream>
     5 #include <cstdio>
     6 #include <cstdlib>
     7 #include <cstring>
     8 #include <cmath>
     9 #include <cctype>
    10 #include <algorithm>
    11 #include <vector>
    12 #include <queue>
    13 #include <list>
    14 #include <map>
    15 #include <stack>
    16 #include <set>
    17 #include <string>
    18 
    19 using namespace std;
    20 const int MAX_N = 4e4+10;
    21 const int INF = 1e5;
    22 int a[MAX_N];
    23 int dp[MAX_N];
    24 //然后里面的dp换了含义, dp[i] 代表的是长度为 i 的最小尾部,
    25 //那么我们就可以依靠这个进行不断地更新
    26 
    27 int main(void){
    28     int T;  cin>>T;
    29     while(T--){
    30         int n;  cin>>n;
    31         for(int i = 0; i < n; i++){
    32             scanf("%d", &a[i]);
    33         }
    34         fill(dp, dp + n, INF);
    35         for(int i = 0; i < n; i++){
    36             *lower_bound(dp, dp + n, a[i]) = a[i];
    37         }
    38      //   printf("THE RESULT IS :
    ");
    39         printf("%d
    ", lower_bound(dp, dp + n, INF) - dp);
    40     }
    41     return 0;
    42 }
    View Code
  • 相关阅读:
    python3.4 + pycharm 环境安装 + pycharm使用
    ddt源码修改:HtmlTestRunner报告依据接口名显示用例名字
    re模块
    LeetCode Weekly Contest 12
    求解强连通分量
    几道题-找规律-记录并查找
    欧几里德算法
    树上二分
    几道题-博弈
    随便写一些东西-缩边
  • 原文地址:https://www.cnblogs.com/lucky-light/p/11504534.html
Copyright © 2020-2023  润新知