动态规划
-
核心
状态转移方程 -
特性
最优化子问题: 将复杂的问题分解为若干个子问题,然后综合子问题的最优解来得到原问题的最优解
重叠子问题: 一些问题可以被分解为若干子问题,且这些子问题会重复出现
无后效性:当前状态记录了历史信息,一旦当前状态确定,就不会在改变,且未来的决策只能在已有的一个或若干个状态的基础上进行,历史状态只能通过已有的信息去影响未来
- 递归写法
以斐波那契举例
若直接暴力求解,时间复杂度为O(2^n)
// n为输入的斐波那契数列第n项
int f(int n)
{
if(n==0||n==1) return 1;
else return f(n-1)+f(n-2);
}
递归写法也称为记忆化搜索
// 设置dp数组存储
const int maxn=1000;
int dp[maxn];
int f(int n)
{
if(n==0||n==1) return 1;
if(dp[n]!=-1) return dp[n];
else {
dp[n]=f(n-1)+f(n-2);
return dp[n];
}
}
- 递推写法
int n; //输入数据
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
for(int j=1;j<=i;++j)
{
scanf("%d",&f[i][j]);
}
}
// 边界
for(int i=n-1;i>=i;--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];
}
}
printf("%d\n",dp[1][1]);
return 0;
}
//输入样例
5
5
8 3
12 7 16
4 10 11 6
9 5 3 9 4
以下例子中:细节说明求解动态规划的方法
1. 最大连续子序列和
给定一个数字序列a1,a2,a3,…,求i,j(1<=i<=j<=n),使得ai+…a j(连续的)最大,输出这个最大和
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=10000+10;
int a[maxn],dp[maxn];
int main()
{
freopen("C:\\Users\\24398\\Desktop\\in-project.txt","r",stdin);
int n;
scanf("%d",&n);
for(int i=0;i<n;++i)
{
scanf("%d",&a[i]);
}
// 边界
dp[0]=a[0];
for(int i=1;i<n;++i)
{
//状态转移方程
dp[i]=max(a[i],dp[i-1]+a[i]);
}
sort(dp,dp+n);
cout<<dp[n-1]<<endl;
return 0;
}
//输出示例
5
1 2 -4 5 3
//输出
8
2. 最长不下降子序列
在一个数字序列中,找到一个最长的子序列(可以不连续),使得这个子序列是下降的(非递减的)
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
const int n=100;
int a[n],dp[n];
int main()
{
int n; cin>>n;
for(int i=0;i<n;++i)
{
cin>>a[i];
}
int ans=0; //记录
for(int i=1;i<=n-1;++i)
{
dp[i]=1;
for(int j=1;j<i;++j){
if(a[i]>=a[j]&&(dp[j]+1>dp[i])){
dp[i]=dp[j]+1;
}
}
ans=max(ans,dp[i]);
}
cout<<ans<<endl;
return 0;
}