【基础练习】【线性DP】codevs1408 最长公共子序列(上升)题解

</pre><p></p><p style="color:rgb(54,46,43); font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px">这道题目捣鼓了一个小时了终于弄出来咯···怒吼三声:容易吗!文章被盗还是很严重,加版权信息转载请注明出处 [ametake版权所有]http://blog.csdn.net/ametake欢迎来看</span></p><p style="color:rgb(54,46,43); font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px">先放题目</span></p><p style="color:rgb(54,46,43); font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px"></span></p><div class="panel panel-default" style=""><div class="panel-heading font-bold" style="border-color:rgb(237,241,242); background-color:rgb(246,248,248); font-weight:700; padding:10px 15px; border-bottom-width:1px; border-bottom-style:solid; color:rgb(51,51,51)"><span style="">题目描述 <small style="font-size:13px">Description</small></span></div><div class="panel-body" style="padding:15px"><p style="margin-top:0px; margin-bottom:10px">熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们要研究最长公共上升子序列了。<br style="" />小沐沐说,对于两个串A,B,如果它们都包含一段位置不一定连续的数字,且数字是严格递增的,那么称这一段数字是两个串的公共上升子串,而所有的公共上升子串中最长的就是最长公共上升子串了。<br style="" />奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子串。不过,只要告诉奶牛它的长度就可以了。</p></div></div><div class="panel panel-default" style=""><div class="panel-heading font-bold" style="border-color:rgb(237,241,242); background-color:rgb(246,248,248); font-weight:700; padding:10px 15px; border-bottom-width:1px; border-bottom-style:solid; color:rgb(51,51,51)"><span style="">输入描述 <small style="font-size:13px">Input Description</small></span></div><div class="panel-body" style="padding:15px"><p style="margin-top:0px; margin-bottom:10px">第一行N,表示A,B的长度。<br style="" />第二行,串A。<br style="" />第三行,串B。</p></div></div><div class="panel panel-default" style=""><div class="panel-heading font-bold" style="border-color:rgb(237,241,242); background-color:rgb(246,248,248); font-weight:700; padding:10px 15px; border-bottom-width:1px; border-bottom-style:solid; color:rgb(51,51,51)"><span style="">输出描述 <small style="font-size:13px">Output Description</small></span></div><div class="panel-body" style="padding:15px"><p style="margin-top:0px; margin-bottom:10px">输出长度</p></div></div><div class="panel panel-default" style=""><div class="panel-heading font-bold" style="border-color:rgb(237,241,242); background-color:rgb(246,248,248); font-weight:700; padding:10px 15px; border-bottom-width:1px; border-bottom-style:solid; color:rgb(51,51,51)"><span style="">样例输入 <small style="font-size:13px">Sample Input</small></span></div><div class="panel-body" style="padding:15px"><p style="margin-top:0px; margin-bottom:10px">4<br style="" />2 2 1 3<br style="" />2 1 2 3</p></div></div><div class="panel panel-default" style=""><div class="panel-heading font-bold" style="border-color:rgb(237,241,242); background-color:rgb(246,248,248); font-weight:700; padding:10px 15px; border-bottom-width:1px; border-bottom-style:solid; color:rgb(51,51,51)"><span style="">样例输出 <small style="font-size:13px">Sample Output</small></span></div><div class="panel-body" style="padding:15px"><p style="margin-top:0px; margin-bottom:10px">2</p></div></div><div class="panel panel-default" style=""><div class="panel-heading font-bold" style="border-color:rgb(237,241,242); background-color:rgb(246,248,248); font-weight:700; padding:10px 15px; border-bottom-width:1px; border-bottom-style:solid; color:rgb(51,51,51)"><span style="">数据范围及提示 <small style="font-size:13px">Data Size & Hint</small></span></div><div class="panel-body" style="padding:15px"><p style="margin-top:0px; margin-bottom:10px">1<=N<=3000,A,B中的数字不超过maxlongint</p></div></div>看到这道题目,首先想到的是“最长公共子序列”<p></p><p style="color:rgb(54,46,43); font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px">最长公共子序列思路是:</span></p><p style="color:rgb(54,46,43); font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px">f[i][j]表示a的前i个和b的前j个中的最长公共子序列长度</span></p><p style="color:rgb(54,46,43); font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px">转移方程为:</span></p><p style="color:rgb(54,46,43); font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px">xi = yj 时 , f[i,j] = f[i-1,j-1] + 1</span></p><p style="color:rgb(54,46,43); font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px">xi <> yj时 , f[i,j] = max { f[i,j-1] , f[i-1,j] }</span></p><p style="color:rgb(54,46,43); font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px"></span></p><p style="color:rgb(54,46,43); font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px">【【但是由于本题要求的是最长公共上升子序列,上升这个要求让他必须参考末尾元素的值,因此这个方程是不对的】】</span></p><p style="color:rgb(54,46,43); font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px">我们先放上最长公共上升子序列的方程,比较一下:</span></p><p style="color:rgb(54,46,43); font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px"> a[i]!=b[j]:   F[i][j]=F[i-1][j] a[i]==b[j]:   F[i][j]=max(F[i-1][k])+1 1<=k<=j-1&&b[j]>b[k] </span></p><p style="font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px"><span style="color:rgb(54,46,43)">这是什么意思?这里f[i][j]的意思发生了改变,他指的是:</span><span style="font-family:Arial; font-size:14px; line-height:24px; text-indent:28px"><span style="color:#362e2b">a的前i个和b的前j个中</span><strong><span style="color:#ff6666">以b[j]为结尾</span></strong><span style="color:#362e2b">的最长公共子序列</span></span></span></p><p style="font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px"><span style="color:#362e2b">【为什么要以b[j]结尾?</span><strong><span style="color:#ff0000">如果不以b[j]结尾,那么我们求出的f[i-1][k]也不一定是k结尾,所谓f[i][j]只是表示“<span style="font-family:Arial; font-size:14px; line-height:24px; text-indent:28px">a的前i个和b的前j个中的最长公共子序列长度</span>”</span></strong></span></p><p style="font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px"><strong><span style="color:#ff0000">这样就无法保证我们选择的b[k]<b[j] 也就无法保证子序列单调递增</span></strong><span style="color:#362e2b">】</span></span></p><p style="font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px"><span style="color:#362e2b">那么方程又进行了怎样的改变呢?</span></span></p><p style="font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px"><span style="color:#362e2b">对于方程的解释,请允许我引用</span><strong><span style="color:#000099">@我们都爱刘汝佳</span></strong><span style="color:#362e2b"> 的见解,原文地址在补充第三条</span></span></p><p style="font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px"><span style="color:#990000"><strong>“首先,在a[i]!=b[j]的时候有F[i][j]=F[i-1][j]。为什么呢?因为F[i][j]是以b[j]为结尾的LCIS,如果F[i][j]>0那么就说明a[1]..a[i]中必然有一个字符a[k]等于b[j](如果F[i][j]等于0呢?那赋值与否都没有什么影响了)。因为a[k]!=a[i],那么a[i]对F[i][j]没有贡献,于是我们不考虑它照样能得出F[i][j]的最优值。所以在a[i]!=b[j]的情况下必然有F[i][j]=F[i-1][j]。这一点参考LCS的处理方法。 那如果a[i]==b[j]呢?首先,这个等于起码保证了长度为1的LCIS。然后我们还需要去找一个最长的且能让b[j]接在其末尾的LCIS。之前最长的LCIS在哪呢?首先我们要去找的F数组的第一维必然是i-1。因为i已经拿去和b[j]配对去了,不能用了。并且也不能是i-2,因为i-1必然比i-2更优。第二维呢?那就需要枚举b[1]..b[j-1]了,因为你不知道这里面哪个最长且哪个小于b[j]。这里还有一个问题,可不可能不配对呢?也就是在a[i]==b[j]的情况下,需不需要考虑F[i][j]=F[i-1][j]的决策呢?答案是不需要。因为如果b[j]不和a[i]配对,那就是和之前的a[1]..a[j-1]配对(假设F[i-1][j]>0,等于0不考虑),这样必然没有和a[i]配对优越。(为什么必然呢?因为b[j]和a[i]配对之后的转移是max(F[i-1][k])+1,而和之前的i`配对则是max(F[i`-1][k])+1。显然有F[i][j]>F[i`][j],i`>i) 于是我们得出了状态转移方程: a[i]!=b[j]:   F[i][j]=F[i-1][j] a[i]==b[j]:   F[i][j]=max(F[i-1][k])+1 1<=k<=j-1&&b[j]>b[k]”</strong></span></span></p><p style="color:rgb(54,46,43); font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px"></span></p><p style="color:rgb(54,46,43); font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px">因此核心代码为:</span></p><p style="color:rgb(54,46,43); font-family:Arial; font-size:14px; line-height:26px"><span style="line-height:24px; text-indent:28px"></span></p><pre name="code" class="cpp">for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=n;j++)
		{
			if (a[i]==b[j])
			{
				f[i][j]=1;//remember or if there's not miner before it will be 0见下方说明
				for (int k=j-1;k>=1;k--)//反向查找更快
				{
					if (b[k]<b[j])
					{
						f[i][j]=max(f[i][j],f[i-1][k]+1);
					}
				}
			}
			else
			{
				f[i][j]=f[i-1][j];//make sure it ends with b[j] 在此过程中,b[j]结尾没有变
			}
		}
    }

最后,只要在所有的f[n][j]中选择最大的就可以,因为f[n][j]一定是以b[j]结尾的公共上升子序列中最大的

那么放上O(n3)朴素版本代码

接下来是平方级的代码,详情见下面补充:

至于空间压缩为一位的算法,详情还是看链接吧,我在这里只放出核心代码:

for(i=1;i<=n1;i++)
{
    max=0;
	for(j=1;j<=n2;j++)
	{
	    if (a[i]>b[j]&&max<f[j]) max=f[j];
	    if (a[i]==b[j]) f[j]=max+1;
	}
}

最后请让我补充几个坑点:

1.输入数据是数字而不是字符串,每个数之间都有空格,按字符串读入会挂掉,因为字符串把空格当成结尾= =

2.如果i=j,变量k循环前,要先把f[i][j]赋值为1,否则如果找不到比当前j小的k的话,f[i][j]是0,会导致wa

3.本题O(n3)算法可以胜任,但还有平方算法和一维空间算法 详情参见http://wenku.baidu.com/view/3e78f223aaea998fcc220ea0.html上文中的解释也引自这篇文章 再次感谢@我们都爱刘汝佳
同时感谢多多帮助的里奥君~

——亦余心之所善兮,虽九死其犹未悔

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-13 13:14:44

【基础练习】【线性DP】codevs1408 最长公共子序列(上升)题解的相关文章

UVA 10100- Longest Match(dp之最长公共子序列)

题目地址:UVA 10100 题意:求两组字符串中最大的按顺序出现的相同单词数目. 思路:将字串中的连续的字母认作一个单词,依次计算出两个字符串中的单词,其中第1个字符串的单词序列为t1.word[1]-..t1.word[n],第2个字符串的单词序列为t2.word[1]-..t2.word[m].然后将每个单词当成一个字符,使用LCS算法计算出两个字符串的最长公共子序列,该序列的长度就是最长匹配. #include <stdio.h> #include <math.h> #in

dp之最长公共子序列

例1:给你两个字符串,找出最长子序列的长度. 对于字符串t, 字符串s,给定特定的i, j代表t,s的位置,只存在三种情况: 1. i == 0 ||  j==0, M[i][j] = 0; 2. t[i] == s[j], M[i][j] = min(M[j-1][j-1] + 1, M[i-1][j], M[i][j-1]);//.....不知道怎么解释.... 3. t[i] != s[j] , M[i][j] = min(M[i-1][j], M[i][j-1]);//..... 经典d

【DP】最长公共子序列

Description 字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列.令给定的字符序列X="x0,x1,-,xm-1",序列Y="y0,y1,-,yk-1"是X的子序列,存在X的一个严格递增下标序列<i0,i1,-,ik-1>,使得对所有的j=0,1,-,k-1,有xij = yj.例如,X="ABCBDAB",Y="BCDB"是X的一个子序列. 对给

(hdu step 3.2.2)Common Subsequence(简单dp:求最长公共子序列的长度)

在写题解之前给自己打一下广告哈~..抱歉了,希望大家多多支持我在CSDN的视频课程,地址如下: http://edu.csdn.net/course/detail/209 题目: Common Subsequence Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 976 Accepted Submission(s): 538   Probl

线性动态规划——解最长公共子序列问题

动态规划法 经常会遇到复杂问题不能简单地分解成几个子问题,而会分解出一系列的子问题.简单地采用把大问题分解成子问题,并综合子问题的解导出大问题的解的方法,问题求解耗时会按问题规模呈幂级数增加(分治思想,递归方法.往往会由于数据大导致递归层次过多而超时或爆栈,即使采用记忆化等优化策略,仍然可能解决不了问题). 为了节约重复求相同子问题的时间,引入一个数组,不管它们是否对最终解有用,把所有子问题的解存于该数组中,找出数组中相关元素之间存在的关系(动态转移方程),这就是动态规划法所采用的基本方法. [

POJ 1458 - Common Subsequence(最长公共子序列) 题解

此文为博主原创题解,转载时请通知博主,并把原文链接放在正文醒目位置. 题目链接:http://poj.org/problem?id=1458 题目大意: 有若干组数据,每组给出两个字符串(中间用任意数量的空格间隔),输出这两个字符串最长公共子序列的长度.每次输出后换行. 分析: 动态规划求LCS,f[i][j]表示第一个字符串匹配到第i位,第二个字符串匹配到第j位时最长公共子序列的长度. 转移方程:当a[i] = b[i]时,f[i][j] = f[i-1][j-1]+1,其他情况时f[i][j

hdu1159-Common Subsequence(DP:最长公共子序列LCS)

Common Subsequence Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 49216    Accepted Submission(s): 22664 Problem Description A subsequence of a given sequence is the given sequence with some el

[dp]LCS最长公共子序列

https://www.51nod.com/tutorial/course.html#!courseId=4 复杂度:${\rm O}(nm)$ 转移方程: 1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 int n,m; 5 int dp[1002][1002]; 6 char path[1002]; 7 string s,t; 8 int main(){ 9 cin>>s>

Codeforcs 1114D Flood Fill (区间DP or 最长公共子序列)

题意:给你n个颜色块,颜色相同并且相邻的颜色块是互相连通的(连通块).你可以改变其中的某个颜色块的颜色,不过每次改变会把它所在的连通块的颜色也改变,问最少需要多少次操作,使得n个颜色块的颜色相同. 例如:[1, 2, 2, 3, 2]需要2步:[1, 2, 2, 3, 2] -> [1, 2, 2, 2, 2] -> [2, 2, 2, 2, 2]. 思路:我们先把颜色块压缩(即把本来颜色相同并且相邻的颜色块合并).容易发现,如果出现了形如[1, 2, 3, 1]这种情况, 那么显然先合并两个