poj 2127 Greatest Common Increasing Subsequence

本文深入解析了LCIS问题及其状态转移方程,并通过实例展示了如何求解以特定元素为终点的最长上升子序列。此外,文章还提供了一种O(nlogn)算法解决最长公共上升子序列问题,结合实例代码进行详细说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 我们假设数组A和B分别为这两个序列,我们定义状态dp[i][j]表示以A[i]和B[j]结尾的并且LCIS的最后一个元素小于A[i]的值,也就是说dp[i][j]并不一定是串A[1--i]和串B[1--j]的LCIS,因为多了一个限制就是该LCIS的最后一个元素必须小于A[i]。那么闲杂我们看看如何来进行状态转移呢?
       如果A[i] == B[j],那么dp[i][j] = max(dp[i`][j-1]) +1;(1<i`<i)
       否则dp[i][j]  = dp[i][j-1]

http://blog.youkuaiyun.com/wesweeky/article/details/6358950


 解题思路:如何把这个问题分解成子问题呢?经过分析,发现 “求以ak(k=1, 2, 3…N)为终点的最长上升子序列的长度”是个好的子问题――这里把一个上升子序列中最右边的那个数,称为该子序列的“终点”。虽然这个子问题和原问题形式上并不完全一样,但是只要这N 个子问题都解决了,那么这N 个子问题的解中,最大的那个就是整个问题的解。由上所述的子问题只和一个变量相关,就是数字的位置。因此序列中数的位置k 就是“状态”,而状态 k 对应的“值”,就是以ak 做为“终点”的最长上升子序列的长度。这个问题的状态一共有N 个。状态定义出来后,转移方程就不难想了。假定MaxLen (k)表示以ak 做为“终点”的最长上升子序列的长度,那么:
MaxLen (1) = 1

MaxLen (k) = Max { MaxLen (i):1<i < k 且 ai < ak 且 k≠1 } + 1


最长上升子序列(LIS)长度的O(nlogn)算法


http://www.slyar.com/blog/longest-ordered-subsequence.html

【经典问题】最长公共上升子序列


http://www.clarkok.com/2011/08/%E3%80%90%E7%BB%8F%E5%85%B8%E9%97%AE%E9%A2%98%E3%80%91%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E4%B8%8A%E5%8D%87%E5%AD%90%E5%BA%8F%E5%88%97/

procedure work;
var
    i,j,k:integer;
begin
    for i:= 1 to n do
        begin
            k:=0;
            for j:= 1 to m do
                begin
                    if a[i] = b[j] && f[j] < f[k]+1 then //b[k] 一定满足<a[i] ,见下面改变k的条件
                            f[j]:=f[k]+1;

                    if a[i] > b[j] && f[k] <f[j] then     
                            k:=j;                  // 更新新的k
                end;
        end;
    for i:= 1 to m do
        if ans < f[i] then
            ans:=f[i];
end;


2 根据网上写出的O(n*m)算法,有待加强理解,仍然WA

#include <iostream>
#include<stdio.h>
#include <cstring>
#include <list>

using namespace std;

#define MAX_N (500+1)

int dp[MAX_N];//设F[j]为必选择B[j]为末尾时的最长
int pre[MAX_N][MAX_N];
int N[MAX_N],M[MAX_N];

#define printf //
int n,m;
//同时用二维数组pre[i][j]存储上个最长长度是以seq2[]的哪个结尾的,假设之前的最长长度是dp[i'][j'],则pre[i][j] = j'
int solve()
{
	memset(dp,0,sizeof(dp));
	memset(pre,0,sizeof(pre));


	dp[0]=0;

	int maxp = 0,maxi,maxj;
	//dp[i][j] 表示长度为i,长度为j的字串的最长上升子序列的长度,其中上升子序列中最大元素必须是N[i],即第一个字串的第i个元素
	for(int i=1;i<=n;i++)
	{
		int k=0;
		int maxv= N[i];
		for(int j=1;j<=m;j++)
		{
			if(M[j] == maxv&& dp[j]< dp[k] + 1)
			{
				dp[j] = dp[k] +1;

				pre[i][j]=k;

				printf("[%d][%d]=%d dp[%d]=%d\n",i,j,k,j,dp[j]);

			}

			if(M[j] < maxv && dp[j] > dp[k])
			{
				k = j; //k为 序列里最大的元素小于 N[i]的最长递增子序列对应的j的记录
			}

			if(dp[j] > maxp)
			{
				maxp = dp[j];
				maxi=i;
				maxj=j;
			}
		}
	}

	int _m=0;
	while( true)
	{
		if(maxi < 0 || maxj <0)
			break;

		while(maxj > 0)
		{
			M[_m++] = M[maxj];	
			printf("maxi=%d,maxj=%d\n",maxi,maxj);
			maxj = pre[maxi][maxj];
		}
		maxi--;
		printf("out while,maxi=%d,maxj=%d\n",maxi,maxj);	
	}


	cout<<maxp<<" "<<endl;

	for(int j=_m-1;j>=0;j--)
		cout<<M[j]<<" ";

	cout<<endl;
	return 0;
}

int main()
{
	while(cin>>n)
	{
		for(int i=1;i<=n;i++)
			cin>>N[i];
		
		cin>>m;
		for(int j=1;j<=m;j++)
			cin>>M[j];	
		solve();
	}
}


1 --------------------------------------自己写出的O( n*m*n*m)算法
 
#include <iostream>
#include<stdio.h>
#include <cstring>
#include <list>

using namespace std;

#define MAX_N (500+1)

int dp[MAX_N][MAX_N];
int dpMax[MAX_N][MAX_N];
int N[MAX_N],M[MAX_N];

struct point{
	int i;
	int j;
	int flag;
};
point dpPrint[MAX_N][MAX_N];
int n,m;
int get_max(int n,int m,int &maxp,int &maxii,int &maxjj,int &maxn)
{
	int _maxp = 0;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(dp[i][j] > _maxp)
			{
				_maxp = dp[i][j];
				maxii=i;
				maxjj=j;
				maxp=_maxp;
				maxn = dpMax[i][j];
			}

		}
	}

	return 0;

}
int solve()
{
	memset(dp,0,sizeof(dp));

	for(int i=1;i<=n;i++)
	{
		dp[i][0] = 0;

		
	}
	for(int j=1;j<=m;j++)
	{
		dp[0][j] = 0;
	}

	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(N[i] == M[j])
			{
				//if(i > 1)//
				int maxp=0;
				int maxii=0;
				int maxjj=0;

				for(int ii=1;ii<i;ii++)
				{
					for(int jj=1;jj<j;jj++)
					{

						if( dp[ii][jj] > maxp && N[i] > dpMax[ii][jj])
						{
							maxp = dp[ii][jj];
							maxii=ii;
							maxjj=jj;
						}
					}
				}

				dp[i][j] = maxp +1;
				dpMax[i][j] = N[i];
				dpPrint[i][j].i=maxii;
				dpPrint[i][j].j = maxjj;

				dpPrint[i][j].flag = 3;
			}
			else
			{
				int maxp1=0,maxii1,maxjj1,max1;
				int maxp2=0,maxii2,maxjj2;

				get_max(i,j,maxp1,maxii1,maxjj1,max1);
				
				if(maxp1 > 0)
				{
					dp[i][j] = maxp1;
					dpMax[i][j] = max1;
					dpPrint[i][j].i=maxii1;
					dpPrint[i][j].j=maxjj1;
				}
				
			}
		}
	}

	int maxp = 0;
	int maxii=0;
	int maxjj=0;

	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(dp[i][j] > maxp)
			{
				maxp = dp[i][j];
				maxii = i;
				maxjj = j;
			}

	cout<<maxp<<" "<<endl;

	m=0;
	while(maxii > 0 && maxjj > 0)
	{
		point p = dpPrint[maxii][maxjj];

		if(p.flag == 3)
			M[m++] = N[maxii];

		maxii = p.i;
		maxjj = p.j;
	}

	for(int _m = m-1;_m>=0;--_m)
		cout<< M[_m]<<" ";


	cout<<endl;
	return 0;
}

int main()
{
	while(cin>>n)
	{
		for(int i=1;i<=n;i++)
			cin>>N[i];
		
		cin>>m;
		for(int j=1;j<=m;j++)
			cin>>M[j];	
		solve();
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值