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

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

 

  背包问题 一 、   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\n", dp[N][W]);
36     return 0;
37 }
View Code

 

   最长公共子序列问题:

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

    dp[ i + 1] [ j + 1] : 这个指的是 s0[ i ] 与 s0[ 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\n", dp[len0][len1]);
32     return 0;
33 }
34 
35 /*
36 abcd
37 becd
38 
39 
40 */
41  
View Code

 

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

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

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

    为了保持正向进行, 我们的 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 ]);  

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

 

 

 

 

 0-1 背包问题二

 

 

 

多重部分和问题

 

 

最长上升子序列问题    

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

  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 :\n");
45         printf("%d\n", 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 :\n");
39         printf("%d\n", lower_bound(dp, dp + n, INF) - dp);
40     }
41     return 0;
42 }
View Code

 

转载于:https://www.cnblogs.com/lucky-light/p/11504534.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值