学习笔记之动态规划(一)最长上升子序列及最长公共子序列

例题:最长上升子序列(百练2757)
问题描述
一个数的序列ai,当a 1 < a 2 < … < a S 的时候,我们称这个序列是上升的。对于给定的一个序列(a 1 , a 2 , …, a N ),我们可以得到一些上升的子序列(a i1 , a i2 , …, a iK ),这里1 <= i1 <i2 < … < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).
你的任务,就是对于给定的序列,求出最长上升子序列的长度。
输入数据
输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。
输出要求
最长上升子序列的长度。
输入样例
7
1 7 3 5 9 4 8
输出样例
4

  1. 找子问题
    “求序列的前n个元素的最长上升子序列的长度”是个子问题,但这样分解子问题,不具有“无后效性”假设F(n) = x,但可能有多个序列满足F(n) = x。有的序列的最后一个元素比 a n+1 小,则加上a n+1 就能形成更长上升子序列;有的序列最后一个元素不比a n+1 小……以后的事情受如何达到状态n的影响,不符合“无后效性”
  2. 找子问题
    “求以a k (k=1, 2, 3…N)为终点的最长上升子序列的长度”一个上升子序列中最右边的那个数,称为该子序列的“终点”。
    虽然这个子问题和原问题形式上并不完全一样,但是只要这N个子问题都解决了,那么这N个子问题的解中,最大的那个就是整个问题的解。
  3. 确定状态:
    子问题只和一个变量-- 数字的位置相关。因此序列中数的位置k 就是“状态”,而状态 k 对应的“值”,就是以a k做为“终点”的最长上升子序列的长度。状态一共有N个。
  4. 找出状态转移方程:
    maxLen (k)表示以a k 做为“终点”的最长上升子序列的长度那么:
    初始状态:maxLen (1) = 1
    maxLen (k) = max { maxLen (i):1<=i < k 且 a i < a k 且 k≠1 } + 1若找不到这样的i,则maxLen(k) = 1maxLen(k)的值,就是在a k 左边,“终点”数值小于a k ,且长度最大的那个上升子序列的长度再加1。因为a k 左边任何“终点”小于a k 的子序列,加上a k 后就能形成一个更长的上升子序列。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN =1010;
int a[MAXN]; int maxLen[MAXN];
int main() {
	int N; cin >> N;
		for( int i = 1;i <= N;++i) {
			cin >> a[i]; maxLen[i] = 1;
}
	for( int i = 2; i <= N; ++i) {// 每次求以第i 个数为终点的最长上升子序列的长度
		for( int j = 1; j < i; ++j)// 察看以第j 个数为终点的最长上升子序列
		if( a[i] > a[j] )
			maxLen[i] = max(maxLen[i],maxLen[j]+1);
	}
		cout << * max_element(maxLen+1,maxLen + N + 1 );
return 0;
} 

动归的常用两种形式
1)递归型
优点:直观,容易编写
缺点:可能会因递归层数太深导致爆栈,函数调用带来额外时间开销。无法使用滚动数组节省空间。总体来说,比递推型慢。
1)递推型
效率高,有可能使用滚动数组节省空间

例、最长公共子序列(POJ1458)
给出两个字符串,求出这样的一个最长的公共子序列的长度:子序列中的每个字符都能在两个原串中找到,而且每个字符的先后顺序和原串中的先后顺序一致。

Sample Input
abcfbc abfcab
programming contest
abcd mnp
Sample Output
4
2
0
输入两个串s1,s2,
设MaxLen(i,j)表示:
s1的左边i个字符形成的子串,与s2左边的j个字符形成的子串的最长公共子序列的长度(i,j从0开始算)
MaxLen(i,j) 就是本题的“状态”假定 len1 = strlen(s1),len2 = strlen(s2)那么题目就是要求 MaxLen(len1,len2)显然:
MaxLen(n,0) = 0 ( n= 0…len1)
MaxLen(0,n) = 0 ( n=0…len2)
递推公式:
if ( s1[i-1] == s2[j-1] ) //s1的最左边字符是s1[0]
MaxLen(i,j) = MaxLen(i-1,j-1) + 1;
else
MaxLen(i,j) = Max(MaxLen(i,j-1),MaxLen(i-1,j) );
时间复杂度O(mn) m,n是两个字串长度,S1长度为 i,S2长度为 j,S1[i-1]!= s2[j-1]时,MaxLen(S1,S2)不会比MaxLen(S1,S2 j-1 )和MaxLen(S1 i-1 ,S2)两者之中任何一个小,也不会比两者都大。

#include <iostream>
#include<cstring>
using namespace std;

char s1[1000],s2[1000];
int maxlen[1000][1000];
int main(){
cin>>s1>>s2;
int s1n=strlen(s1),s2n=strlen(s2);
for(int i=0;i<=s1n;++i)
	maxlen[i][0]=0;
for(int i=0;i<=s2n;++i)
	maxlen[0][i]=0;
for(int i=1;i<=s1n;++i)
	for(int j=1;j<=s2n;++j){
		if(s1[i-1]==s2[j-1])maxlen[i][j]=maxlen[i-1][j-1]+1;
		else maxlen[i][j]=max(maxlen[i-1][j],maxlen[i][j-1]);
	}
	cout<<maxlen[s1n][s2n]<<endl;
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章