当初学者最开始学习 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 ]) ;

 #include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <algorithm>
#include <vector>
#include <queue>
#include <list>
#include <map>
#include <stack>
#include <set>
#include <string> using namespace std;
const int MAX_N = ;
const int MAX_W = ;
int dp[MAX_N][MAX_W];
int N, W;
int w[MAX_N], value[MAX_N];
int main()
{
cin>>N>>W;
for(int i = ; i < N; i++) cin>>w[i]>>value[i];
dp[N][] = ;
fill(dp[N], dp[N] + W, );
for(int i = N - ; i >= ; i--){
for(int j = ; j <= W; j++){
if(j < w[i])
dp[i][j] = dp[i+][j];
else
dp[i][j] = max(dp[i+][j], dp[i+][j-w[i]] + value[i]);
}
}
return ;
}

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

  

 #include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <algorithm>
#include <vector>
#include <queue>
#include <list>
#include <map>
#include <stack>
#include <set>
#include <string> using namespace std;
const int MAX_N = ;
const int MAX_W = ;
int dp[MAX_N][MAX_W];
int N, W;
int w[MAX_N], value[MAX_N];
int main()
{
cin>>N>>W;
for(int i = ; i < N; i++) cin>>w[i]>>value[i];
fill(dp[], dp[] + W, );
for(int i = ; i < N; i++){
for(int j = ; j <= W; j++){
if(j < w[i])
dp[i+][j] = dp[i][j];
else
dp[i+][j] = max(dp[i][j], dp[i][j-w[i]] + value[i]);
}
}
   printf("THE RESULT : %d\n", dp[N][W]);
return ;
}

   最长公共子序列问题:

  那么我们完全可以把看看成追赶问题, 而且联想 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])   //这个就是因为最后两个不相等, 不可能同时使用, 所以说我们删去一个 , 还可以减小问题的规模!

  

 #include <iostream>
#include <cstring>
#include <bits/stdc++.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <cctype>
#include <cmath>
using namespace std;
const int MAX_S = ;
char s0[MAX_S], s1[MAX_S];
int len1, len0;
int dp[MAX_S][MAX_S];
int main()
{
cin>>s0>>s1;
len0 = strlen(s0);
len1 = strlen(s1);
fill(dp[], dp[] + MAX_S, );
for(int i = ; i < len0; i++){
for(int j = ; j < len1; j++){
if(s0[i] == s1[j]){
dp[i+][j+] = dp[i][j] + ;
}
else{
dp[i+][j+] = max(dp[i+][j], dp[i][j+]);
}
}
}
printf("THE ANSWER IS THAT : %d\n", dp[len0][len1]);
return ;
} /*
abcd
becd */

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

    那么对于这个类似于 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 = ;
int dp[MAX][MAX];
int w[MAX], v[MAX];
int W, N; //这个是对完全背包问题的解决
//分为了两种解法
int main()
{
int N; cin>>N>>W;
for(int i = ; i < N; i++){
scanf("%d%d", w + i, v + i);
}
memset(dp, , sizeof(dp)); //小小的初始化一手
//不节省空间的写法
for(int i = ; i < N; i++){
for(int j = ; j <= W; j++){
if(j-w[i] >= )
dp[i+][j] = max(dp[i][j], dp[i+][j-w[i]]+v[i]);
else
dp[i+][j] = dp[i][j];
}
}
printf("THE FIRST RESULT IS %d\n", dp[N][W]); //节省空间的写法
memset(dp, , sizeof(dp)); //再次进行初始化
for(int i = ; i < N; i++){
for(int j = ; j <= W; j++){
if(j-w[i] >= )
dp[][j] = max(dp[][j], dp[][j-w[i]]+v[i]);
else
dp[][j] = dp[][j];
}
}
printf("THE SECOND RESULT IS %d\n", dp[][W]); return ;
}
/*
3 7
3 4
4 5
2 3 THE RESULT SHOULD BE 10 */

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

多重部分和问题

最长上升子序列问题    

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

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

  代码

 /*
将本题目抽象出来居然是 最长上升子序列问题
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <algorithm>
#include <vector>
#include <queue>
#include <list>
#include <map>
#include <stack>
#include <set>
#include <string> using namespace std;
const int MAX_N = 4e4+;
int a[MAX_N];
int dp[MAX_N]; int main()
{
int T; cin>>T;
while(T--){
int n; cin>>n;
for(int i = ; i < n; i++){
scanf("%d", &a[i]);
}
memset(dp, , sizeof(dp));
int res = ;
dp[] = ;
for(int i = ; i < n; i++){
dp[i] = ;
for(int j = ; j < i; j++){
if(a[i] > a[j]){
dp[i] = max(dp[i], dp[j] + );
res = max(res, dp[i]);
}
}
}
printf("THE RESULT :\n");
printf("%d\n", res);
}
return ;
}

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

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

  lower_bound : ai >= k  的最小指针

  upper_bound : ai >k  的最小指针

 /*
将本题目抽象出来居然是 最长上升子序列问题
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <algorithm>
#include <vector>
#include <queue>
#include <list>
#include <map>
#include <stack>
#include <set>
#include <string> using namespace std;
const int MAX_N = 4e4+;
const int INF = 1e5;
int a[MAX_N];
int dp[MAX_N];
//然后里面的dp换了含义, dp[i] 代表的是长度为 i 的最小尾部,
//那么我们就可以依靠这个进行不断地更新 int main(void){
int T; cin>>T;
while(T--){
int n; cin>>n;
for(int i = ; i < n; i++){
scanf("%d", &a[i]);
}
fill(dp, dp + n, INF);
for(int i = ; i < n; i++){
*lower_bound(dp, dp + n, a[i]) = a[i];
}
// printf("THE RESULT IS :\n");
printf("%d\n", lower_bound(dp, dp + n, INF) - dp);
}
return ;
}

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

  1. 动态规划之最长公共子序列LCS(Longest Common Subsequence)

    一.问题描述 由于最长公共子序列LCS是一个比较经典的问题,主要是采用动态规划(DP)算法去实现,理论方面的讲述也非常详尽,本文重点是程序的实现部分,所以理论方面的解释主要看这篇博客:http://b ...

  2. 最长公共子序列 nlogn

    先来个板子 #include<bits/stdc++.h> using namespace std; , M = 1e6+, mod = 1e9+, inf = 1e9+; typedef ...

  3. 动态规划1——最长递增子序列、最长公共子序列、最长公共子串(python实现)

    目录 1. 最长递增序列 2. 最长公共子序列 3. 最长公共子串 1. 最长递增序列 给定一个序列,找出其中最长的,严格递增的子序列的长度(不要求连续). 解法一:动态规划 通过一个辅助数组记录每一 ...

  4. 删除部分字符使其变成回文串问题——最长公共子序列(LCS)问题

    先要搞明白:最长公共子串和最长公共子序列的区别.    最长公共子串(Longest Common Substirng):连续 最长公共子序列(Longest Common Subsequence,L ...

  5. lintcode :最长公共子串

    题目 最长公共子串 给出两个字符串,找到最长公共子串,并返回其长度. 样例 给出A=“ABCD”,B=“CBCE”,返回 2 注意 子串的字符应该连续的出现在原字符串中,这与子序列有所不同. 解题 注 ...

  6. poj 2774 Long Long Message,后缀数组,求最长公共子串 hdu1403

    题意:给出两个字符串,求最长公共子串的长度. 题解:首先将两个字符串连在一起,并在中间加一个特殊字符(字串中不存在的)切割,然后两个串的最长公共字串就变成了全部后缀的最长公共前缀.这时就要用到heig ...

  7. SPOJ1811最长公共子串问题(后缀自动机)

    题目:http://www.spoj.com/problems/LCS/ 题意:给两个串A和B,求这两个串的最长公共子串. 分析:其实本题用后缀数组的DC3已经能很好的解决,这里我们来说说利用后缀自动 ...

  8. poj2774 Long Long Message 后缀数组求最长公共子串

    题目链接:http://poj.org/problem?id=2774 这是一道很好的后缀数组的入门题目 题意:给你两个字符串,然后求这两个的字符串的最长连续的公共子串 一般用后缀数组解决的两个字符串 ...

  9. 求字符串A与字符串B的最长公共字符串(JAVA)

    思路:引入一个矩阵的思想,把字符串A(长度为m)当成矩阵的行,把字符串B(长度为n)当矩阵的列.这样就构成一个m*n的矩阵.若该矩阵的节点相应的字符同样,即m[i]=n[j]时.该节点值为1:当前字符 ...

  10. 最长公共子序列(LCS)问题 Longest Common Subsequence 与最长公告字串 longest common substr

    问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列.令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk ...

随机推荐

  1. 系统信息的管理函数API

    1.Windows系统信息 1.1获取系统版本:   BOOL WINAPI GetVersionEx( __in_out LPOSVERSIONINFO lpVersionInfo ); lpVer ...

  2. OpenGL+VS2010环境配置及遇到的问题

    OpenGL+VS2010+GLUT工具包+WIN10系统: 第一步,安装GLUT工具包 Windows环境下的GLUT下载地址:(大小约为150k) http://www.opengl.org/re ...

  3. allowMultiQueries=true mybatis 要多行sql执行,一定要注意

    allowMultiQueries=true 这个配置已经出现多次问题了,这次由于切换时多数据源,搞配置的同志不知道从哪里copy的配置,只换了我们的链接,我们之前配置了好多配置都丢失了,我的代码中有 ...

  4. ubuntu 16.04 脚本开机自启动

    1.首先编写一个shell脚本文件,例如python_self_start.sh (nohup & 指定后台运行) #!/bin/bash nohup python3 /home/senset ...

  5. 安装VMware虚拟机和centos操作系统

    1,安装包:百度网盘: 2,安装教程:https://blog.csdn.net/qq_31362105/article/details/80706096  配置好操作系统,内存,网络等: 3,Cen ...

  6. Spring Security整合JWT,实现单点登录,So Easy~!

    前面整理过一篇 SpringBoot Security前后端分离,登录退出等返回json数据,也就是用Spring Security,基于SpringBoot2.1.4 RELEASE前后端分离的情况 ...

  7. Visual Studio Code(VS Code)命令行的使用(1)

    在终端中输入如下命令,打开VS Code界面. code 在终端中输入如下命令,打印出 VS Code 命令行所支持的所有参数. code --help 在 code 命令后加上文件或者文件夹的地址, ...

  8. css简单学习属性2---背景图片

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  9. sqlmap注入工具----一次简单的注入(实战)

    最近在学习网络安全,挖洞避免不了要使用许多工具,使用著名注入工具sqlmap的时候,对于英语不怎么好的我就比较难受了,本来就不会使用,加之又是英语的,简直难受.上网找了好多详细教程,但是命令实在是太多 ...

  10. 解析Python编程中的包结构

    解析Python编程中的包结构 假设你想设计一个模块集(也就是一个"包")来统一处理声音文件和声音数据.通常由它们的扩展有不同的声音格式,例如:WAV,AIFF,AU),所以你可能 ...