天天看点

POJ2533 Longest Ordered Subsequence 最长升序子序列

最长升序子序列一般有两种解法,一种是经典的动态规划方法,复杂度为O(n^2),另外一种方法则借助栈和二分查找,复杂度为O(nlgn)。

动态规划

设d[i]表示以a[i]结尾的最长升序子序列的长度,则可以得到状态转移方程为d[i]=max{1,max{d[j]}+1},其中j=0,1,2,...i-1并且a[j]<a[i]。用一句话概括思路就是每次计算d[i]时,就要扫描数组a中前i-1个元素,如果遇到a[j]>a[i]则跳过a[j]继续扫描j...i-1的元素,否则如果a[j]<a[i]则用以a[j]为结尾的子序列长度+1就是以a[j]为倒数第二个元素,a[i]为倒数第一个元素的最长子序列长度。之所以要求a[j]<a[i]很明显是因为要求的是升序子序列,之所以max{d[j]}是因为要求的是最长的子序列,所以要找到前i-1个元素中子序列长度最大的然后+1。

栈+二分查找

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

这种方法很直观,比较好理解,用一句话说就是顺次扫描数组,如果a[i]大于栈顶的元素则入栈,否则在栈中找到第一个大于a[i]的元素t并用a[i]替换t。如

1 7 3 5 9 4 8

栈为1 7 的时候,输入3,则要替换7变为1 3,然后输入5大于栈顶的3,直接入栈变为1 3 5=>1 3 5 9=>1 3 4 9=>1 3 4 8。最后的top就是最长升序子序列的长度。

AC_CODE:

#include <stdio.h>

#define N 1001

//动态规划
int a[N];
int dp[N];

/************************************************************************/
/* 0MS 156K*/
/************************************************************************/
int lis(int n)
{
	int i,j,max;
	for(i=0;i<n;i++)
		dp[i]=1;
	for(i=1;i<n;i++)
	{
		max=-1;
		//j从0开始
		for(j=0;j<i;j++)
		{
			if(a[j]>=a[i]) continue;
			max=(dp[j]>max)?dp[j]:max;
			dp[i]=(max+1>1)?max+1:1;
		}
	}
	max=-1;
	for(i=0;i<n;i++)
		max=(dp[i]>max)?dp[i]:max;
	return max;
}

/************************************************************************/
/* 16MS 156K*/
/************************************************************************/
int a[N];
int stack[N];

int lis(int n)
{
	int top,i,l,m,r;
	top=-1;
	stack[++top]=a[0];
	for(i=1;i<n;i++)
	{
		if(a[i]>stack[top])
			stack[++top]=a[i];
		else
		{
			l=0;r=top;
			while(l<=r)
			{
				m=(l+r)>>1;
				if(stack[m]<a[i])
					l=m+1;
				else
					r=m-1;
			}
			stack[l]=a[i];
		}
	}
	return top+1;
}
int main()
{
	int n,i;
	scanf("%d",&n);
	for(i=0;i<n;i++)
		scanf("%d",&a[i]);
	printf("%d\n",lis(n));
	return 0;
}