动态规划专题

本文详细介绍了动态规划的算法思想,包括其共性特征(重复子问题与最优子结构),并以矩阵链相乘、钢条切割、最长公共子串、最长公共子序列、背包问题、最大子数组等经典问题为例,展示了如何定义状态、建立状态转移方程和确定赋值顺序。通过实际代码演示,深入理解动态规划在信息技术中的实践应用。
摘要由CSDN通过智能技术生成

动态规划

算法思想

动态规划是一种解决最优化问题的算法,较为官方的讲,动态规划是一种能够将一个复杂的问题分解为若干个子问题,最后综合子问题的最优解来得到原问题最优解的过程。
即这类问题所要考虑的重点便为能否划分为子问题解决以及如何划分这两大问题。

能否划分为子问题解决

首先考虑能否划分,即动态规划算法所具有的共性,重复子问题以及最优子结构。
重复子问题: 即我们所要求解的问题中对于这个问题的过程值我们需要反复进行计算获取来使用,这种情况我们往往通过记录下子问题值的方式来进行算法时间上的优化,典型例子:斐波那契数列。
**最优子结构:**所求问题的最优解包含子问题的最优解。

如何划分

找准状态的定义以及状态转移方程的建立,在此基础上明确边界以及赋值顺序等细节问题。

应用实例

一、矩阵链相乘问题 (输出最终结合方式)

在这里插入图片描述
实现思路
状态的定义:dp[i][j] : 表示为从第i个矩阵乘到第j个矩阵所需要的最少的乘法次数。
此题的最优化点为矩阵相乘所需要的最少的乘法次数,将问题更加具体来讲便是求得从第0个矩阵到最后一个矩阵所需的最少乘法次数,因此,我们不难想到,它的子问题既是小规模情况下所需要的最少的乘法次数,思考到这里以后,接下来便是思考有了小规模的最优解能否求得大规模的最优解,以及小规模的最优解会不会受到矩阵增加的影响
小规模的最优解显然不会受到矩阵增加的影响,因为无论矩阵增加多少个,这个小规模是一定的,定义好了的,它的状态不会发生改变。接下来,便是思考递推公式即状态转移方程的确定。
状态转移方程:dp[i][j] = min( i <= k < j)(dp[i][k] + dp[k+1][j] + p[i-1]*p[k]*p[j])
有了状态转移方程后,整体思路已经差不多成型了,还有一点细节但也非常重要的便是边界的确定,即初始化以及求值的顺序问题
由于该问题我们最终所要求解的问题是第0个矩阵到最后一个矩阵的最优化问题,即子问题是因此在赋值顺序的过程中,我们需要拥有,中间段的子问题的值,正三角显然行不通,因此采取倒三角的赋值方式。
代码实现

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

// U1U2U3U4U5U6;2,3,7,9,5,2,4
#define MAXNUM 1e6

int N;
vector< vector<int> > dp;
vector< vector<int> > show;
struct node {
	int a;
	int b;
};
struct node num[1000];
void add(int i, int j, int k)
{
	if (j - i <= 0)
		return;
	//在知道了,i,j,k;判断他们三者的距离来决定要不要加括号
	if (k - i > 0)
	{
		num[i].a++;
		num[k].b++;
	}
	if (j - k > 1)
	{
		num[k + 1].a++;
		num[j].b++;
	}
	add(i, k, show[i][k]);
	add(k + 1, j, show[k + 1][j]);
}

void matrix_multi(string input) {
    bool is_num = false;
    vector<int> matrix_dimension;
    for(int i = 0;i < input.size(); ++i) {
       
        if(input[i] == ';') {
            is_num = true;
            continue;
        }

        if(!is_num) {
            continue;
        }
 
        int tmp_value = 0;
        while(input[i] != ',') {
            tmp_value = tmp_value*10 + (input[i] - '0');
            ++i;
            if (i >= input.size()) {
                break;
            }
        }
        matrix_dimension.push_back(tmp_value);
    }
    N = matrix_dimension.size() - 1;
    dp.resize(N+1,vector<int>(N+1, MAXNUM));
    show.resize(N+1, vector<int>(N+1, 0));
    for (int i = 1; i <= N; ++i) {
        dp[i][i] = 0;
    }
    for (int i = N; i >= 1; --i) {
        for (int j = i + 1; j <= N; ++j) {
            dp[i][j] = dp[i][i] + dp[i + 1][j] + matrix_dimension[i - 1]*matrix_dimension[i]*matrix_dimension[j];
            show[i][j] = i;
            for (int k = i + 1; k < j; ++k) {
                int tmp_val = dp[i][k] + dp[k + 1][j] + matrix_dimension[i - 1]*matrix_dimension[k]*matrix_dimension[j];
                if (tmp_val < dp[i][j]) {
                    dp[i][j] = tmp_val;
                    show[i][j] = k;
                }
            }
        }
    }
    add(1, N, show[1][N]);
    printf("(");
    for (int i = 1; i <= N; i++)
	{
		while (num[i].a != 0)
		{
			num[i].a--;
			printf("(");
		}
		printf("U%d", i);
		while (num[i].b != 0)
		{
			num[i].b--;
			printf(")");
		}
	}
    printf(")");
    //cout << dp[1][N] << endl;
}

int main() {
    string input;
    cin >> input;
    matrix_multi(input);
    return 0;
}

输入以及输出结果:
输入:U1U2U3U4U5U6;2,3,7,9,5,2,4
输出:在这里插入图片描述

二、钢条切割问题

在这里插入图片描述
实现思路
状态的定义:dp[i]:钢条长度为i时切割所能获得的最大收益。
状态转移方程:dp[i] = max(0 <= j < i)(dp[i-j] + dp[j])
赋值顺序:正三角
代码实现:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

// 10;1,1;2,5;3,8;4,9;5,10;6,17;7,17;8,20;9,24;10,24;

int N = -1;
vector<int> value;
vector<int> dp;
void print() {
    for(int i = 0;i < value.size(); ++i) {
        cout << value[i] << " ";
    }
    cout << endl;
}

void stealbar_splite(string input) {
    
    for (int i = 0; i < input.size(); ++i) {
        if(N == -1) {
            int tmp_val = 0;
            while (input[i] != ';') {
                tmp_val = tmp_val*10 + (input[i] - '0');
                ++i;
            }
            N = tmp_val;
            continue;
        }
        bool is_num = false;
        while(input[i] != ';') {
            if (input[i] == ',') {
                is_num = true;
                ++i;
                continue;
            }
            if(!is_num) {
                ++i;
                continue;
            }
            int tmp_val = 0;
            while(input[i] != ';') {
                tmp_val = tmp_val*10 + (input[i] - '0');
                ++i;
            }
            value.push_back(tmp_val);           
        }
    }
    
    dp.resize(N + 1, 0);
    dp[1] = value[0];
    // 算法核心
    for (int i = 2;i <= N; ++i) {
        dp[i] = value[i - 1];
        for (int j = 1; j < i; ++j) {
            if (dp[i] < dp[j] + dp[i - j]) {
                dp[i] = dp[j] + dp[i - j];
            }
        }
    }
    cout << dp[N] << endl;
}

int main() {
    string input;
    cin >> input;
    stealbar_splite(input);
    return 0;
}

输入以及输出结果
输入:10;1,1;2,5;3,8;4,9;5,10;6,17;7,17;8,20;9,24;10,24;
输出:27

三、最长公共子串(输出公共子串)

在这里插入图片描述

实现思路
状态的定义:dp[i][j]:第一个字符串前i个字符与第二个字符串前j个字符的最长子串数目。
状态转移方程:dp[i][j] = (v[i] == v[j]) ? (dp[i-1][j-1] + 1) : 0
赋值顺序:正矩形
代码实现:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

// A,B,E,A,D,F,C;A,C,E,A,D,B
vector<char> str1;
vector<char> str2;
vector< vector<int> > dp;

void common_substring_longest(string input) {
    bool is_secondstr = false;
    for (int i = 0; i < input.size(); ++i) {
        if (input[i] == ';') {
            is_secondstr = true;
            continue;
        }
        if (!is_secondstr) {
            if (input[i] != ',') {
                str1.push_back(input[i]);
            }
            continue;
        }
        if (input[i] != ',') {
            str2.push_back(input[i]);
        }
    }
    int n1 = str1.size();
    int n2 = str2.size();
    dp.resize(n1 + 1, vector<int>(n2 + 1, 0));
    int max_v = -1;
    int l_index = 0;
    int r_index = 0;
    // 算法核心
    for (int i = 1; i <= n1; ++i) {
        for (int j = 1; j <= n2; ++j) {
            if (str1[i - 1] == str2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            }
            else {
                dp[i][j] = 0;
            }
            if (max_v < dp[i][j]) {
                max_v = dp[i][j];
                l_index = i;
                r_index = j;
            }
        }
    }
    int l_begin = l_index - max_v;
    for(int i = l_begin; i < l_index; ++i) {
        cout << str1[i];
        if (i != l_index - 1) {
            cout << ",";
        }
    }
}

int main() {
    string input;
    cin >> input;
    common_substring_longest(input);
    return 0;
}

输入以及输出
输入:A,B,E,A,D,F,C;A,C,E,A,D,B
输出:E、A、D

四、最长公共子序列(输出最长子序列)

在这里插入图片描述

实现思路
状态的定义:dp[i][j]:第一个字符串前i个字符与第二个字符串前j个字符的最长公共子序列数目。
状态转移方程:dp[i][j] = (v[i] == v[j]) ? (dp[i-1][j-1] + 1) : max(dp[i-1][j], dp[i][j-1])
赋值顺序:正矩形
代码实现:

#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;

// A,B,C,B,D,A,B;B,D,F,A,B,A

vector<char> str1;
vector<char> str2;
vector< vector<int> > dp;
vector< vector<int> > output;
queue<char> ans;
void print(int x, int y) {
    if (x >= 0 && y >= 0) {
        if(output[x][y]==1)
        {
            print(x-1,y-1);
            ans.push(str1[x]);
        }
        if(output[x][y]==2)
            print(x - 1, y);
        if(output[x][y]==3)
            print(x, y - 1);
    }
}

void common_subsequence_longest(string input) {
    bool is_secondstr = false;
    for (int i = 0; i < input.size(); ++i) {
        if (input[i] == ';') {
            is_secondstr = true;
            continue;
        }
        if (!is_secondstr) {
            if (input[i] != ',') {
                str1.push_back(input[i]);
            }
            continue;
        }
        if (input[i] != ',') {
            str2.push_back(input[i]);
        }
    }
    int n1 = str1.size();
    int n2 = str2.size();
    dp.resize(n1 , vector<int>(n2, 0));
    output.resize(n1 , vector<int>(n2, 0));
	// 算法核心
    for (int i = 0;i < n1; ++i) {
        for (int j = 0; j < n2; ++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;
                }
                output[i][j] = 1;
            }
            else {
                if (i == 0 && j == 0) {
                    dp[i][j] = 0;
                }
                else if (i == 0 && j != 0) {
                    dp[i][j] = dp[i][j-1];
                    output[i][j] = 3;
                }
                else if (i != 0 && j == 0) {
                    dp[i][j] = dp[i - 1][j];
                    output[i][j] = 2;
                }

                else {
                    if (dp[i - 1][j] > dp[i][j - 1]) {
                        output[i][j] = 2;
                        dp[i][j] = dp[i - 1][j];
                    }
                    else {
                        output[i][j] = 3;
                        dp[i][j] = dp[i][j - 1];
                    }
                }

            }
        }
    }
    print(n1 - 1, n2 - 1);
    while(!ans.empty()) {
        cout << ans.front();
        ans.pop();
        if (! ans.empty()) {
            cout << ",";
        }
    }
//    cout << dp[n2][n1] << endl;
}

int main() {
    string input;
    cin >> input;
    common_subsequence_longest(input);
    return 0;
}

输入以及输出
输入:A,B,C,B,D,A,B;B,D,F,A,B,A
输出:B,D,A,B

五、背包问题

在这里插入图片描述
实现思路
状态的定义:dp[i][j]:将前i种商品装入大小为j的背包所能获得的最大价值。
状态转移方程:dp[i][j] = (v[i] > j) ? (dp[i-1][j]) : max(dp[i-1][j], (dp[i - 1][j - v[i] + p[i]))
赋值顺序:正矩形
代码实现:

#include<iostream>
#include<stdlib.h>
#include<string>
#include<vector>
#include<stack>
#include<cmath>
#include<algorithm>
using namespace std;

int C;
vector<int> vi,pi;

int power(int a,int b)
{
    int sum = 1;
    for(int i = 1; i <= b; i++)
    {
            sum = sum*a;
    }
    return sum;
}

void GetArr(string s)
{
     int n = s.length();
     
     // 获取C的值 
     int i = 0;
     while(s[i]!=';')
     {
   	    C = C*10 + (s[i]-'0');
   	    i++;
     }
     i++;
     
     // 获取vi,pi的值 
     int v,p;
     while(i<n)
     {
        v = 0;
        p = 0;
        
        while(i<n && s[i]!=',')
        {
 		   v = v*10 + (s[i]-'0');
 		   i++;
        }
        vi.push_back(v);
        i++;
        
        while(i<n && s[i]!=';')
        {
		 	p = p*10 + (s[i]-'0');
			i++;	  
		}
		pi.push_back(p);
		i++;
     }
}

int Max(int a, int b)
{
 	if(a>b)
 	{return a;}
 	else{return b;}
}

int KNAPSACK(int row,int col)
{
   vector< vector<int> > dp;
   dp.resize(row, vector<int>(col, 0));
   
   for(int i = 1; i < row;i++)
   {
      for(int j = 1; j < col; j++)
      {
  		  dp[i][j]=dp[i-1][j];
  		  if(vi[i]<=j)
  		  {
 			  dp[i][j]= Max(dp[i][j],dp[i-1][j-vi[i]]+pi[i]);
	      }
      }
   }
   return dp[row-1][col-1];
}
int main()
{
    string input;
    cin>>input;
    //输入格式化 
    GetArr(input);
    
    // 背包问题求解
	int value = KNAPSACK(vi.size()+1,C+1);
	cout<<value; 
    return 0;
}

输入以及输出
输入:13;10,24;3,2;4,9;5,10;4,9;
输出:28

六、最大子数组

在这里插入图片描述

实现思路
状态的定义:dp[i]:以第i个数字为结尾的数组的最大子数组和为dp[i]。该题扩展为以最后一个数字结尾的最大子数组的和。
状态转移方程:dp[i] = (dp[i-1] < 0) ? (v[i] : dp[i-1] + v[i])
赋值顺序:顺序遍历
代码实现:

#include<iostream>
#include<stdlib.h>
#include<string>
#include<vector>
#include<stack>
#include<cmath>
#include<algorithm>
using namespace std;

vector<int> arr;

int power(int a,int b)
{
    int sum = 1;
    for(int i = 1; i <= b; i++)
    {
            sum = sum*a;
    }
    return sum;
}

void GetArr(string s)
{
     stack<int> store;
     int n = s.length();
     int i = n-1;
     
     while (i>=0)
     {
         int count=0;
         int sum = 0;
         while(i>=0 && s[i]!=',')
         {
           if(s[i]=='-')
           { sum = -1*sum;
             i--;
           }
           else{
                 sum += (s[i]-'0')*power(10,count);
                 count+=1;
                 i--;
                }
         }
         store.push(sum);
         i--;
     }
     
     n = store.size();
     for(int j = 0; j < n; j++)
     {
        arr.push_back(store.top());
        store.pop();
     }
}


int main() {
    string input;
    cin >> input;
    // 输入格式化 
    GetArr(input);
    
    // 动态规划数组 
    vector<int> sum;
    for(int i = 0; i < arr.size(); i++)
    {
       sum.push_back(0);
    }
    
    sum[0]=arr[0];
    for(int i = 1; i < arr.size(); i++)
    {
        if(sum[i-1]<0)
        {
            sum[i] = arr[i];
        }else{sum[i]=sum[i-1]+arr[i];}
    }
    
    // 找最大子数组和 
    int max_sum=sum[0];
    int max_index = 0;
    for(int i = 1; i < sum.size();i++)
    {
        if(sum[i]>max_sum)
        { max_sum = sum[i];
          max_index = i;}
    }

    // 寻找数组坐标范围
    
    int min_index = max_index;
    int min_sum = max_sum;
    while(min_sum!=0)
    {
        min_sum = min_sum-arr[min_index];
        min_index-=1;      
    } 
    cout<<"X["<<min_index+1<<","<<max_index<<"]="<<max_sum<<endl;
    return 0;
} 

输入以及输出
输入:-1,-3,3,5,-4,3,2,-2,3,6
输出:X[2,9]=16

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值