动态规划专题

21 阅读2分钟

动态规划

一个问题可以分解为若干个子问题,且这些子问题会重复出现,称这个问题拥有重叠子问题
一个问题的最优解,可以由其子问题的最优解有效构造出来,称为这个问题拥有最优子结构
一个问题拥有重叠子问题最优子结构才能用动态规划解决问题

  1. 定义状态
  2. 定义状态转移方程
  3. 定义边界
  4. 求解问题

01题 数塔问题

题目描述

image.png 样例输入

5
5
8 3
12 7 16
4 10 11 6
9 5 3 9 4

样例输出

44

Code

递推实现 自底向上

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000;
int f[maxn][maxn], dp[maxn][maxn];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			cin>>f[i][j];
		}
	}
	//边界
	for(int i=1;i<=n;i++){
		dp[n][i]=f[n][i];
	} 
	for(int i=n-1;i>=1;i--){
		for(int j=1;j<=i;j++){
			//状态转移方程
			dp[i][j]=max(dp[i+1][j], dp[i+1][j+1])+f[i][j];
		}
	}
	cout<<dp[1][1]<<endl;
	return 0;
 } 

递归实现 自顶向下

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000;
int f[maxn][maxn], dp[maxn][maxn];
int n;
int Fn(int x,int y)
{
    //边界
    if(x==n) return f[x][y];
    if(dp[x][y]!=-1) return dp[x][y];
    else{
        dp[x][y]=max(Fn(x+1,y), Fn(x+1,y+1) )+f[x][y];//状态转移方程
        return dp[x][y];
    }
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i;j++){
            cin>>f[i][j];
        }
    }
    //初始化 dp数组 
    memset(dp, -1, sizeof(dp));
    //边界
    for(int i=1;i<=n;i++){
        dp[n][i]=f[n][i];
    } 
    int ans=Fn(1,1);
    cout<<ans<<endl;
    return 0;
} 

02题 最大连续子序列和

题目描述

image.png 样例输入

 6
 -2 11 -4 13 -5 -2

样例输出

20

Code

递归实现

#include<bits/stdc++.h>
using namespace std;
const int maxn=10010;
int f[maxn], dp[maxn];
int Fn(int x)
{
	if(x==0) return f[0];
	if(dp[x]!=-1) return dp[x];
	else{
		dp[x]=max(f[x], Fn(x-1)+f[x]);//状态转移方程
		return dp[x];
	} 
}
int main()
{
	int n;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>f[i];
	}
	memset(dp, -1, sizeof(dp));
	//边界条件 
	dp[0]=f[0];
	Fn(n);
	int k=0;
	for(int i=0;i<n;i++){
		if(dp[i]>dp[k])
			k=i;
	}
	cout<<dp[k]<<endl;
	return 0;
}

03题 最长不下降子序列(LIS)

问题描述

image.png 样例输入

8
1 2 3 -9 3 9 0 11

样例输出

6

Code

#include<bits/stdc++.h>
using namespace std;
const int maxn=100;
int f[maxn], dp[maxn];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>f[i];
	}
	int ans=-1;
	for(int i=1;i<=n;i++){
		dp[i]=1;//边界初始条件
		for(int j=1;j<i;j++){
			if(f[j]<=f[i] && (dp[j]+1>dp[i])){
				dp[i]=dp[j]+1;//状态转移方程
			}
		}
		ans=max(ans,dp[i]);
	}
	cout<<ans<<endl;
	return 0;
} 

04题 最长公共子序列(LCS)

题目描述

image.png 样例输入

sadstory
adminsorry

样例输出

6

Code

#include<bits/stdc++.h>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 100;
char A[N], B[N];
int dp[N][N];
int main()
{
	gets(A+1);
	gets(B+1);
	int lenA = strlen(A + 1);
	int lenB = strlen(B + 1);
	//边界
	for(int i=0; i<=lenA; i++){
		dp[i][0]=0;
	} 
	for(int j=0; j<=lenB; j++){
		dp[0][j]=0;
	} 
	//状态转移方程
	for(int i=1; i<=lenA; i++){
		for(int j=1; j<=lenB; j++){
			if(A[i] == B[j]){
				dp[i][j]=dp[i-1][j-1]+1;
			}else{
				dp[i][j]=max(dp[i-1][j], dp[i][j-1]);
			}
		}
	} 
	cout<<dp[lenA][lenB]<<endl;
	return 0;
 } 

05题 最长回文子串

题目描述

image.png 样例输入

PATZJUJZTACCBCC

样例输出

9

Code

#include<bits/stdc++.h>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1010;
char S[N];
//dp[i][j]表示S[i]到S[j]是否为回文串,是为1,否则为0 
int dp[N][N];
int main()
{
	gets(S);
	int len = strlen(S), ans = 1;
	//初始化 
	memset(dp, 0, sizeof(dp));
	//边界条件 
	for(int i = 0; i < len; i++){
		dp[i][i] = 1;
		if(i < len - 1){
			if(S[i] == S[i+1]){
				dp[i][i+1]=1;
				ans=2;
			}
		}
	}
	//状态转移方程
	for(int L = 3; L <= len; L++){//枚举子串的长度 
		for(int i = 0; i + L - 1 < len; i++){//枚举子串的起始端点 
			int j = i + L - 1;//子串的右端点 
			if(S[i] == S[j] && dp[i+1][j-1] == 1){
				dp[i][j]=1;
				ans = L;
			}
		}
	}	
	cout<<ans<<endl;
	return 0;
 }