【dp优化】LIS(最长上升子序列)长度的nlogn算法

先上一道例题:Bridging signals POJ - 1631

这道题第一反应就想到了 [CEOI96]渡轮问题 就是一个非常裸的求最长上升子序列的长度,还不要方案,非常的水。然而,常规的dp复杂度是 O(n^2) ,这道题会愉快地TLE,所以要进行nlogn级别的优化。

//O(n^2) TLE
#include
#include
#include
using namespace std;
#define LL long long
#define INF 0x3f3f3f3f
#define MAXN 40005
int n;
int a[MAXN],dp[MAXN];
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		int ans=-1;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			dp[i]=1;
			for(int j=1;j<i;j++)
				if(a[j]<a[i])
					dp[i]=max(dp[i],dp[j]+1);
			ans=max(ans,dp[i]);
		}
		printf("%d\n",ans);
	}
	return 0;
}

nlogn算法

其实说实话我觉得这个算法比常规的动归思想上更暴力,就是贪心地取,然而复杂度更小。

具体就是:

  • 定义d[k]:长度为k的上升子序列的最末元素,若有多个长度为k的上升子序列,则记录最小的那个最末元素。
  • 枚举a[i] 对每个a[i]:若a[i]>d[len],那么len++,d[len] = a[i];
  • 否则,从d[1]到d[len-1]中找到一个j,满足d[j-1] < a[i]< d[j],则根据d的定义,我们需要更新长度为j的上升子序列的最末元素,即 d[j] = a[i];

(这里实际上就是运用了贪心的思路,d[j-1]< a[i]< d[j]的条件保证了正确性,而对于d中的每一个元素,都尽力做到最小,这样就尽可能地使a[i]>d[len]成立)

//nlogn LIS
#include
#include
#include
using namespace std;
#define LL long long
#define INF 0x3f3f3f3f
#define MAXN 40005
int n;
int a[MAXN];
int d[MAXN];//长度为k的上升子序列的最末元素,若有多个,记录最小
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		int len=1;
		scanf("%d",&a[1]);
		d[1]=a[1];
		for(int i=2;i<=n;i++)
		{
			scanf("%d",&a[i]);
			if(a[i]>d[len])
				len++,d[len]=a[i];
			else 
			{
				int pos=lower_bound(d+1,d+len+1,a[i])-d;
				d[pos]=a[i];
			}
		}
		printf("%d\n",len);
	}
	return 0;
}

你可能感兴趣的:(DP-线性dp-区间dp,贪心,技巧)