线性动态规划

一,概念篇

1,动态规划:通过计算出小问题的最优解,可以推出大问题的最优解,从而可以推出更大问题的最优解,最小问题即是边界情况。

2,子问题(小问题):子问题是一个与原问题有着类似的结构,但规模比原问题小的问题。

3,最优子结构:动态规划的问题一般是求解全局最优解,而全局最优解是由局部的最有解一步一步推出,局部的最优解称为最优子结构。

4,动态规划的基本思想:将待求解的问题划分为若干个阶段(子问题),按顺序求解子问题,子问题的求解为更大子问题的求解提供信息,由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次。

动态规划的核心思想也有减少冗余,对于会发生重叠的子问题只计算一次,去除冗余。

5,状态表示和最优化值。

状态表示是对当前子问题的解的局面的(条件)一种全面的描述。

最优化值是该状态表示下的最优化值(方案值),我们最终能通过其直接或间接得到答案。

6,状态的设计

具有最优化总结构,能够全面描述某一个局面,尽量简洁。

设计状态的关键是充分描述,尽量简洁。

7,动态规划的精髓——状态转移:通过已知的较小问题的最优值得出较大问题的最有值的过程。状态的转移需要满足要考虑到所有的可能性。状态转移的实质是一个DAG,可以把状态抽象成点,转移抽象成边,转移就是从子问题指向当前状态。

8,无后效性:因为动态规划的转移过程是一个DAG,所以保证了当前状态仅由之前的子问题转移而来,而与后面的状态没有什么关系。

9,动态规划的时间复杂度估计

O=状态数*状态转移的复杂度


入门例题不想过多的去讲,就阐述一道最经典的数字三角形来模拟一下动态规划的思路

观察下面的数字金字塔。

写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。

在这里插入图片描述

显然这是一个最有值问题,首先适用于动态规划求解。

状态:这个题只有位置需要记录,位置会影响我们的决策,很显然。

状态转移:显然要从下面到顶部的最大值,肯定要求从这个点左下方来的路径与右下方来的路径哪个和大要哪个,下面的每个点

都是通过这个策略求出的,所以满足最优子结构。


线性动态规划经典——最长上升子序列(LIS)

顾名思义,最长上升子序列就整个序列满足单调递增性质的子序列中长度最长的一个。

比如 序列 5,3,2,6,7,

其中上升的子序列有

5; 3; 2; 6; 7; 2,6;2,6,7; 6,7;3,6;3,7; 3,6,7;5,6;5,6,7;5,7;

显然其中最长的子序列是2,6,7或3,6,7或5,6,7。最长上升子序列的长度为3。

如何用动态规划的方法求解最长上升子序列的长度?

方法很简单,我们设一个数组dp[i]表示以a[i]为结尾的最长上升自序列,最后所有dp[i]中最大的那个就是这个序列的LIS。

求解dp数组的方法:

显然,我们为了让序列的长度更长,所以一定要把a[i]接在它能接在的dp[j]最大的a[j]后面,因此我们就得到了求解dp数组的状态转移方程:

dp[i]=max{ dp[j] | a[j]<a[i] && j<i } +1 ;

因此我们就得到了一个O(n^2 )的做法:

for(int i=1;i<=n;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; 
}

若数据范围较大,显然O(n^2 )的做法恐怕不行,那么我们是否可以优化一下这个做法,dalao zhaohx讲动态规划的优化分为两种第一种是减少状态量,、

第二种是加快转移过程。加快转移过程又分为性质优化和数据结构优化。LIS可以采用性质优化和数据结构优化两种。

1,性质优化 O(nlogn)

我们仔细观察之前的那个状态转移方程dp[i]=max{ dp[j] | a[j]<a[i] && j<i } +1 ;

我们会发现这个状态转移方程的作用,它每次寻找的是小于a[i]的a[j]中dp[j]最大的那一个,此时我们可以采用一个辅助数组h[k]表示,

我们设h[k]表示dp[j]==k的所有j当中的最小的a[j],就是说长度为k的最长上升序列,最后一个元素的最小值是多少,因为最后一个元素越小,

肯定后面更容易再加上一个元素了。

因此我们发现了一个性质,h[k]一定是单调递增的,当我们新加入一个元素的时候,如果这个元素比末尾元素大,那么这个元素就可以接在末尾

之后即h[++k]=a[i];如果这个元素小于末尾的元素,我们就要给这个元素找一个位置,位置满足的性质是这个元素a[i]能接在某个h[k]之后,且这

个元素要小于h[k+1],因为这样后面的元素才更容易接上,这里我们运用到了二分查找来修改。
算法流程:

如果这个元素大于d[len],直接让d[len+1]=a[i],然后len++。这个很好理解,当前最长的长度变成了len+1,而且d数组也添加了一个元素。

如果这个元素等于d[len],那么可以保证d[1…len-1]都是小于a[i]的(根据上面的证明),因此这个元素就没有什么意义了,直接忽略就好,

因为它无法接在任何一个元素d后面产生一个更有优势的子序列。

如果这个元素小于d[len],那么就在d数组中找到第一个大于等于它的元素(这个元素必然存在,至少d[len]就是),把这个元素替换成a[i]即可。

它必定可以替换掉一个比它大的但在同一h[k]的元素,比如最特殊的情况它会替换掉h[k]。

for(int i=1;i<=n;i++)
{
	if(num[i]>dp[len]) dp[++len]=num[i];
	else if(num[i]<dp[len])
	{
		int head=1,tail=len;
		while(head<tail)
		{
			int mid=(head+tail)>>1;
			if(dp[mid]>=num[i]) tail=mid;
			else head=mid+1;
		}
		dp[head]=num[i];
	}
}

2,树状数组优化,表示并不会

对类似问题的探讨

类似的还有最长不下降子序列,最长下降子序列以及最长上升子序列。

注意最长不下降子序列和最长上升子序列不同,最长不下降子序列左右元素可以相等。


线性动态规划经典——最长公共子序列

最长公共子序列:子序列与子串不同,可以不连续。

我们可以类比最长上升子序列,设dp[x][y]表示第一个串的前x位与第二个串的前y位的最长公共子序列。

考虑三种情况

1,如果s1[x]不在公共子序列中,那么dp[x][y]=dp[x-1][y];

2,如果s2[y]不在公共子序列中,那么dp[x][y]=dp[x][y-1];

3,如果s1[x]==s2[y],dp[x][y]=dp[x-1][y-1]+1;

综上状态转移方程为三者的最大值。

inline void lcs(char *s1,char *s2)
{
	int s1len=strlen(s1+1);
	int s2len=strlen(s2+1);
	for(int i=1;i<=s1len;i++)
		for(int j=1;j<=s2len;j++)
		{
			dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
			if(s1[i]==s2[j]) dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
		}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值