动态规划(DP),一种用来解决一类最优化问题的算法思想
以Fibonacci为例:(递归)
//传统的递归思路
int Fib(int n)
{
if(n==0||n==1)
return 1;
else
return Fib(n-1)+Fib(n-2);
}
随着n越大,n前面的数被计算的次数重复就越多,复杂度高达O(n^2)
int Fib(int n)
{
int dp[MAXN];//用dp来判断Fib(n)有没有被计算过
memset(dp,-1,sizeof(dp));
if(n==0||n==1)
return 1;
if(dp[n]!=-1)
return dp[n];
else
{
dp[n]=Fib(n-1)+Fib(n-2);//计算Fib(n),保存到dp[n]
return dp[n];
}
}
这样就把复杂度从指数等级降低到了线性等级O(n)
例子:经典的数塔问题:将一些数字排成数塔的形状,现在要从第一层走到第N层,每一层只能走一个,问路上所有数字相加的和是多少?(递推)
#include
#include
using namespace std;
const int maxn=1000;
int s[maxn][maxn],dp[maxn][maxn];//s记录数塔每一个位置的数值
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
scanf("%d",&s[i][j]);
}
}
for(int j=1;j<=n;j++)
{
dp[n][j]=s[n][j];
}
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])+s[i][j];
}
}
printf("%d\n",dp[1][1]);
return 0;
}
可以直接确定结果的部分称为边界,DP 的写法从边界出发,通过状态转移扩散到整个dp数组
如这个题,如果从[1][1]往下走,那么随着n的增加,永远不会到头。但是,如果从下往上走,最后一排的每一个元素的DP总为元素本身,dp[n][j]=s[n][j],走到[1][1]为止.
接下来是序列DP
1.最大连续子序列
给出一个序列A1,A2,A3.....An求i,j,使得Ai+....Aj最大
#include
#include
using namespace std;
const int maxn=10010;
int A[maxn],dp[maxn];
int main()
{
int n;
int m=0;
scanf("%d",&n);
for(int i=0;idp[k])
{
k=i;
}
}
printf("%d\n",dp[k]);
return 0 ;
}
2.最长上升子序列:
LIS 输入n及一个长度为n的数列,求出此序列的最长上升子序列长度。上升子序列指的是对于任意的i 样例输入: 5 4 2 3 1 5 样例输出: 3(最长上升子序列为2, 3, 5) 【分析】 法一:定义dp[i]: 以ai为末尾的最长上升子序列的长度。以ai结尾的上升子序列是: 1° 只包含ai的子序列 2° 在满足j
这二者之一。这样就能得到如下递推关系: dp[i]=max{1, dp[j]+1 | j2)。 合在一起: 求最长下降/不上升/不下降子序列思路同此题,只是判断条件有变化。 3.最长公共子序列LCS 给定两个字符串s1和s2(长度均不超过1000),求出这两个字符串的最长公共子序列的长度。
【分析】定义dp[i][j]:串s1的前i个字符 和 串s2的前j个字符的最长公共子序列长度,则s1…si+1和t1…tj+1对应的公共子列可能是: ①si+1=tj+1时:在s1…si 和 t1…tj的公共子列末尾追加si+1(即LCS长度+1) ②否则可能为s1…si和t1…tj+1的公共子列长度l1 或s1…si+1和t1…tj的公共子列长度l2,二者取较大者。 故状态转移方程为: dp[i+1][j+1]=dp[i][j]+1, max(dp[i][j+1], dp[i+1][j]), 最后dp[len1][len2]即为所求,其中len1、len2分别为串s1和s2的长度。#include
#include
#include