今天是动态规划的第一讲,从概念的理解上并不难,这是一种分阶段求最优值的算法,将复杂问题按阶段分成子问题,枚举子问题各种可能情况,从中找到最优值(利用子问题最优解,解决问题最优解)在找出子问题后利用递归
与贪心算法的相同点:动态规划和贪心算法都是一种递推算法 。均有局部最优解来推导全局最优解 。
与贪心算法不同:动态规划中全局最优解中一定包含某个局部最优解,但不一定包含前一个局部最优解,因此需要记录之前的所有最优解动态规划的关键是状态转移方程,即如何由以求出的局部最优解来推导全局最优解边界条件:即最简单的,可以直接得出的局部最优解
主要解决的问题类型:求解最优并且问题可以分为好几个步骤(子问题)问题具有多阶段决策的特征每阶段都有相应的状态与之对应,描述的状态的量称为状态变量每一个阶段都面临一个决策,选最优每一阶段的最优解问题可以递归地归结为下一个阶段各个可能状态的最优解问题各子问题与原问题具有完全相同的结构剧空间顺序或时间顺序对问题的求解划分阶段描述事物的性质,不同事物有不同的性质,因而用不同的状态来刻画,对问题的求解状态的描述是分阶段的对每个阶段做决策用数学公式描述与阶段相关的状态间的演变规律
解题步骤
1.判断问题是否具有最优子结构性质,若不具备则不能用动态规划
2.把问题分成若干个子问题(分阶段)
3.建立状态转移方程
4.找出边界条件
5.将已知边界值带入方程
6.递归求解
例题:
1.吃金币游戏(方格问题)
小明写了一个简单的吃金币游戏,规则:在一个长方形地图上,玩家每次能从一个方格走到相邻一个方格。玩家控制的角色可以向下或者向右走,但不能向上或向左走。每个方格上都有一定的金币。现在,小明想请你帮他想一个策略,尽可能多的获得金币(从左上角走到右下角可能获得的最大金币数)。
思路:
该长方形地图(m*n),规定只能向下或向右走,求从左下角到右下角可能获得的最大金币数
一共走m+n次(向右走m次,向左走n次)
**子问题:**走到第i行,第j列时获得的最大金币数(思考与谁有关),从第i行第j列向前考虑
这种方格问题,遍历所有方格,把每一个方格的最优解求出来(子问题最优解),从而推出整个问题的最优解
F[a][b]表示到(a,b)点时吃到的最大金币量
F[a][b]=max(F[a-1][b]),F[a][b-1]+coin[a]b
F[m][n]即为所求
实现:
int f[100][100]={0},c;//f为当前行列获得的最大金币数
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
cin>>c;
f[i][j]=max(f[i][j-1],f[i-1][j])+c;
}
cout<
从(1,1)到(m,n)遍历每一个点
2.最长上升子序列 3.最长公共子序列 4.最大子段和问题 5.最大2子段和问题 最后动态规划算法通常基于一个递推公式及一个或多个初始状态。当前子问题的解将由上一次子问题的解推出。使用动态规划来解题只需要多项式时间复杂度,因此它比回溯法、暴力法等要快许多。
一个数的序列bi,当b1 < b2 < … < bS 的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < …
就是对于给定的序列,求出最长上升子序列的长度。
输入数据
输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N 个整数,这些整数的取值范围都在0 到10000。
输出要求
最长上升子序列的长度。
输入样例
7
1 7 3 5 9 4 8
输出样例
4
思路
该序列从第一位开始 序列里的每一个数都有一个最长的上升子序列
子问题:以ak为终点的最长上升子序列的长度
假定maxlen(k)表示以ak 做为“终点”的最长上升子序列的长度,那么:
maxlen(1)=1
maxlen(k)=max{maxlen(i):1
int b[1002];
int amaxlen[1002];//最长上升子序列的长度
int main()
{
int i,j,N;
scanf("%d",&N);
for(i=1;i<=N;i++)
scanf("%d",&b[i]);
amaxlen[1]=1;
for(i=2;i<=N;i++)
{
int nTmp=0;//记录第i个数左边子序列最大长度
for(j=1;jb[j])
{
if(nTmp
给出两个字符串A,B求两个的字符串的最长公共子序列
解决方案
DP[i][j]代表 A串从起点到i位置的字串 和 B串从起点到j位置的字串 的LCS
当A[i]==B[j]时:DP[i][j]=DP[i-1][j-1]+1
当A[i]!=B[j]时:DP[i][j]=MAX(DP[i-1][j],DP[i][j-1])int LCS()
{
dp[0][0]=0
for(int i = 1 ; i < =n ; i++)
for(int j = 1 ; j <= m ; 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]);
return dp[n][m];
}
题目简介
描述:给定 N (1 <= N <= 100000) 个绝对值不大于 1000 的整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[N],求从该序列中取出连续一个子段,使这个子段的和最大。如果某子序列全是负数则定义该子段和为 0。求 max{0,a[i]+a[i+1]+…+a[j]}, 1 <= i <= j <= N
输入:有一个正整数 N,后面紧跟 N 个绝对值不大于 1000 的整数
输出:最大子段和
样例输入:
5 6 -1 5 4 -7
样例输出:
19
问题分析
状态设计:
dp[i] (1 <= i <= N) 表示以 a[i] 结尾的最大连续子段和。
显然,dp[i] >= 0 (1 <= i <= N)
求解目标:
max{dp[i]} (1 <= i <= N)
状态转移方程:
dp[i] = max{a[i],0} (i = 1)
dp[i] = max{dp[i-1] + a[i], 0} (2 <= i <= N)
实现int N;
int a[SIZE];
int dp[SIZE];
int maxSubSum = 0;
dp[1] = Max(a[1], 0);
int i;
for (i = 2; i <= N; ++i)
dp[i] = Max(dp[i - 1] + a[i], 0);
for (i = 1; i <= N; ++i) {
if (maxSubSum < dp[i])
maxSubSum = dp[i];
}
问题分析(1/2)
难道要枚举出所有的可能情况吗?
时间复杂度:O(N ^4)
一般当 N <= 100 时,O(N^4)做法的结果往往是TLE
既然最大 2 子段和问题和最大子段和问题较为类似,考虑能否利用最大子段和问题所求出的结果
状态设计:
dpL[i] 表示以 a[i] 结尾的最大连续子段和。
dpL[i] = max{∑a[x…i], 0},1 <= x <= i <= N
显然,dpL[i] >= 0 (1 <= i <= N)
dpR[i] 表示以 a[j] 开始的最大连续子段和。
dpR[j] = max{∑a[j…y], 0},1 <= j <= y <= N
显然,dpR[j] >= 0 (1 <= j <= N)
求解目标:
max{max{dpL[k]} + max{dpR[l]}} (1 <= k < l <= N)
状态转移方程:
dpL[i] = max{a[i],0} (i = 1)
dpL[i] = max{dpL[i – 1] + a[i], 0} (2 <= i <= N)
dpR[j] = max{a[j],0} (j = N)
dpR[j] = max{dpR[j + 1] + a[j], 0}(1 <= j <= N-1)
算法效率
时间复杂度:O(n)
空间复杂度:O(n)int a[50001],l[50001],r[50002];
int main()
{
int t,n,sum,max;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
l[0]=-10001;
sum=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(l[i-1]>a[i]+sum)
l[i]=l[i-1];
else
l[i]=a[i]+sum;
sum+=a[i];
if(sum<0)
sum=0;
}
r[n+1]=-10001;
sum=0;
max=-200000;
for(int i=n;i!=0;i--)
{
if(r[i+1]>a[i]+sum)
r[i]=r[i+1];
else
r[i]=a[i]+sum;
sum+=a[i];
if(sum<0)
sum=0;
if(l[i]+r[i+1]>max)
max=l[i]+r[i+1];
}
printf("%d\n",max)
}
return 0;
}