动态规划的特点:
 需要在给定约束条件下优化某种指标时,动态规划很有用。
 问题可分解为离散子问题时,可使用动态规划来解决。
 每种动态规划解决方案都涉及网格。
 单元格中的值通常就是你要优化的值。
 每个单元格都是一个子问题,因此你需要考虑如何将问题分解为子问题。
 没有放之四海皆准的计算动态规划解决方案的公式。

因此,划分子问题的方式不同,得到的网格就不同,递推公式自然也就不同。
本博客记录一些常见的动态规划问题,方便查询。

最长公共子串

描述

有两个字符串(可能包含空格),请找出其中最长的公共连续子串,输出其长度。(长度在1000以内)
例如:
输入:acbcbcef 和abcbced
输出:5

划分1

动态规划常见问题汇总——持续更新_算法
递推公式为:

//二维数组遍历循环
if(A[i]==B[j]){
	if(i==0||j==0);
		dp[i][j]=1;
	else
		dp[i][j] = dp[i - 1][j - 1] + 1;
}

划分2

字符串 ADCDADB 和 ABDCDBABB
动态规划常见问题汇总——持续更新_#include_02
递推公式为:

//二维数组遍历循环 从第二行,第二列开始遍历,第一行和第一列不遍历。
if(A[i-1]==B[j-1]){//
		dp[i][j] = dp[i - 1][j - 1] + 1;
}

例题1

题目标题:
计算两个字符串的最大公共字串的长度,字符不区分大小写

输入描述:
输入两个字符串

输出描述:
输出一个整数

示例
输入
asdfas werasdfaswer
输出
6

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
    string str1,str2;
    while(cin>>str1>>str2){
        int len1=str1.size();
        int len2=str2.size();
        int result=0;
        vector<vector<int>>dp(len1,vector<int>(len2,0));
        for(int i=0;i<len1;i++){
            for(int j=0;j<len2;j++){
                if(str1[i]==str2[j]){
                    if(i==0||j==0){
                        dp[i][j]=1;
                    }
                    else{
                        dp[i][j]=dp[i-1][j-1]+1;
                        result=max(dp[i][j],result);
                    }
                }
            }
        }
        cout<<result<<endl;
    }
    return 0;
}

例题2

题目描述
查找两个字符串a,b中的最长公共子串。若有多个,输出在较短串中最先出现的那个。

输入描述:
输入两个字符串

输出描述:
返回重复出现的字符

示例
输入
abcdefghijklmnop
abcsafjklmnopqrstuvw
输出
jklmnop

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
    string str1,str2;
    while(cin>>str1>>str2){
        if(str1.length()>str2.length())
            swap(str1,str2);//若有多个,输出在较短串中最先出现的那个
        int len1=str1.size();
        int len2=str2.size();
        int maxcom=0,pos=0;
        vector<vector<int>>dp(len1+1,vector<int>(len2+1,0));
        for(int i=1;i<=len1;i++){
            for(int j=1;j<=len2;j++){
                if(str1[i-1]==str2[j-1]){
                    dp[i][j]=dp[i-1][j-1]+1;
                    if(dp[i][j]>maxcom){
                        maxcom=dp[i][j];
                        pos=i;
                    }
                }
            }
        }
        cout<<str1.substr(pos-maxcom,maxcom)<<endl;
    }
    return 0;
}
01背包问题

例题1:

有N件物品和一个容量为V的背包。第i件物品的价值是C[i],重量是W[i]。求解将哪些物品装入背包可使价值总和最大。

输入描述:
输入第一行数 N V (1 <=N <=500) (1<= V <= 10000)
输入 N行 两个数字 代表 C W (1 <= C <= 50000, 1 <= W <=10000)

输出描述:
输出最大价值

示例1
输入
5 10
8 6
10 4
4 2
5 4
5 3
输出
19

//参考一楼的  做了一点小小的改动
#include<iostream>
#include<utility>
#include<vector>
using namespace std;
int main()
{
    int N,V;
    while(cin >> N >> V)
    {
        vector<pair<int,int>>goods;
        for(int i = 0; i < N; ++i)
        {
            pair<int,int>tem;//第一个int是价值,第二个是重量
            cin >> tem.first >> tem.second;
            goods.push_back(tem);
        }
        vector<vector<int>> dp(N+1,vector<int>(V+1,0));//划分网格
        for(int i = 1; i < N+1; ++i)//网格的行代表物品
        {
            for(int j = 1; j < V+1; ++j)//网格的列代表子背包容量
            {
                if( goods[i-1].second> j)//如果不下,那就等于上次的最优存储
                    dp[i][j] = dp[i-1][j];
                else{//如果放得下 就取较大值
                    int tmp_best = goods[i-1].first + dp[i-1][j-goods[i-1].second];
                    dp[i][j] = max(tmp_best,dp[i-1][j]);
                }
            }
        }
        //返回最后一个元素就是最优的方案
        cout << dp[N][V] << endl;
    }
    return 0;
}

例题2:

动态规划常见问题汇总——持续更新_算法_03
动态规划常见问题汇总——持续更新_c++_04

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
	vector<pair<int, int>>task = { {20,300},{30,500},{50,620},{30,370},{50,400},{30,450},{40,380},{10,150} };
	const int len1 = task.size();
	int x;
	while (cin >> x) {
		int len2 = x / 10 ;
		vector<vector<int>>dp(len1+1, vector<int>(len2+1, 0));//dp网格
		for (int i = 1; i <= len1; i++) {
			for (int j = 1; j <= len2; j++) {
				if (j*10< task[i - 1].first) {
					dp[i][j] = dp[i - 1][j];
				}
				else {
					int pos =(j * 10 - task[i - 1].first)/ 10 ;
					int tem = task[i-1].second+ dp[i - 1][pos];
					dp[i][j] = max(tem, dp[i - 1][j]);
				}
			}
		}
		cout << dp[len1][len2] << endl;
	}
	return 0;
}
最大递增子序列

描述

给定数组arr,返回arr最长递增子序列长度。比如arr=[2,1,5,3,6,4,8,9,7],最长递增子序列为[1,3,4,8,9],所以返回5

思路

生成与arr长度相等的dp,dp[i]表示在必须以arr[i]这个数结尾的情况下,arr[0…i]中的最大递增子序列长度。
arr: 2 1 5 3 6 4 8 9 7
dp: 1 1 2 2 3 3 4 5 4
求解以i为结尾的最大递增子序列的长度,那么在arr[0]-arr[i-1]中,所有比arr小的数,都有可能成为该最大递增子序列的倒数第二个数,那么在这么多选择中,以哪个数结尾的最大递增子序列最大,那么就选择那个数作为倒数第二个数。

例题1

题目描述
Redraiment是走梅花桩的高手。Redraiment总是起点不限,从前到后,往高的桩子走,但走的步数最多,不知道为什么?你能替Redraiment研究他最多走的步数吗?

输入描述:
输入多行,先输入数组的个数,再输入相应个数的整数

输出描述:
输出结果

样例输入
6
2 5 1 5 4 5
样例输出
3

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
    int cnt;
    while(cin>>cnt){
        int result=0;//记录结果
        vector<int>arr(cnt,0);//数据数组,初始化
        for(int i=0;i<cnt;i++){
            cin>>arr[i];
        }
        vector<int>dp(cnt,1);//动态规划网格数组
        for(int i=1;i<cnt;i++){
            for(int j=0;j<=i-1;j++){
                if(arr[i]>arr[j]){
                    dp[i]=max(dp[j]+1,dp[i]);//更新dp[i]
                }
            }
            result=max(result,dp[i]);//记录最大值
        }
        cout<<result<<endl;
    }
    return 0;
}
最小编辑距离

描述

放个传送门link

例题1

Levenshtein 距离,又称编辑距离,指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。编辑距离的算法是首先由俄国科学家Levenshtein提出的,故又叫Levenshtein Distance。
Ex:
字符串A:abcdefg
字符串B: abcdef
通过增加或是删掉字符”g”的方式达到目的。这两种方案都需要一次操作。把这个操作所需要的次数定义为两个字符串的距离。

要求:
给定任意两个字符串,写出一个算法计算它们的编辑距离。
输入描述:
输入两个字符串
输出描述:
得到计算结果

示例1
输入
abcdefg
abcdef
输出
1

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
    string s1,s2;
    while(cin>>s1>>s2){
        int len1=s1.size();
        int len2=s2.size();
        vector<vector<int>>dp(len1+1,vector<int>(len2+1,0));
        for(int i=1;i<=len1;i++){
            dp[i][0]=i;
        }
        for(int j=1;j<=len2;j++){
            dp[0][j]=j;
        }
        dp[0][0]=0;
        for(int i=1;i<=len1;i++){
            for(int j=1;j<=len2;j++){
                if(s1[i-1]==s2[j-1])
                    dp[i][j]=dp[i-1][j-1];
                else{
                    int tem=min(dp[i-1][j],dp[i][j-1]);
                    dp[i][j]=min(dp[i-1][j-1],tem)+1;
                }
            }
        }
        cout<<dp[len1][len2]<<endl;
    }
    return 0;
}
其他可以用动态规划解决的问题

例题1

题目描述
请编写一个函数(允许增加子函数),计算n x m的棋盘格子(n为横向的格子数,m为竖向的格子数)沿着各自边缘线从左上角走到右下角,总共有多少种走法,要求不能走回头路,即:只能往右和往下走,不能往左和往上走。

输入描述:
输入两个正整数

输出描述:
返回结果

示例1
输入
2
2
输出
6

//先看这个问题的递归解决思路,了解递推公式后,更容易理解这个问题的动态规划方法
/*用递归来做,将右下角看做原点(0, 0),左上角看做坐标(m, n),下图所示:
从(m, n)—>(0, 0)就分两步走:
往右走一步:f(m, n - 1)—>(0, 0) 加上下走一步:f(m - 1, n)—>(0, 0)
注意:但凡是触碰到边界,也就是说f(x, 0)或者f(0,x)都只有一条直路可走了,这里的x是变量哈。
f(m, n) = f(m, n - 1) + f(m - 1, n)
按照这种思想,算法就很简单了,代码如下:*/
#include <iostream>
using namespace std;
int fun(int m,int n){
	if(m==0||n==0)
		return 1;
	else
		return fun(m-1,n)+fun(m,n-1);
}
int main(){
	int m,n;
	while(cin>>m>>n){
		cout<<fun(m,n)<<endl;
	}
	return 0;
}

下面是动态规划解决这个问题的代码

#include <iostream>
#include <vector>
using namespace std;
int main() {
    int m, n;
    while (cin >> m >> n) {
        vector<vector<int> > dp(n + 1, vector<int>(m + 1, 0));
        for (int i = 0; i <= n; i++) {
            for (int j = 0; j <= m; j++) {
                if(i==0||j==0)
                    dp[i][j]=1;
                else
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
                
            }
        }
        cout << dp[n][m] << endl;
    }
    return 0;
}