0
点赞
收藏
分享

微信扫一扫

最长上升子序列优化

幸福的无所谓 2022-04-03 阅读 58
算法

引言

上次我们说了基础的最长上升子序列(看这一篇前可以先看一下最长上升子序列)

这次,我们再说一下如何优化,提高效率

我们先来看一道模板题

题目:

题目描述

输入格式

输出格式

输入样例

3
1 3 2

输出样例

1

数据范围

 

想法

应该很容易想到把问题转化为求最大上升子序列,因为这道题是说修改多少个就能变成严格单调递增,其实就是问这个数列中最大上升子序列,除这一个序列外的就是要修改的数字

最原始的代码如下

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
 
long long a[100001];
int f[100001];
//f[i]表示以第i项结尾的最长上升子序列
int main()
{
	int n;
	cin >> n;
	for(int i = 1;i <= n;i++)
	{
		cin >> a[i];
	}
	int maxn = 0;
	for(int i = 1;i <= n;i++)
	{
		f[i] = 1;//初值
		for(int j = 1;j <= i - 1;j++)
		{
			if(a[j] < a[i])
				f[i] = max(f[i],f[j] + 1);
			maxn = max(maxn,f[i]);
		}
	}
	cout << n - maxn;
	return 0;
}

 (如果没有看懂上面这段代码的话建议看一下我的上一篇题解)

如果只这样写的话,会有一个样例错了

想要得到满分,则必须进行优化,下面,我们来详细讲一下最长上升子序列的优化

优化

我们来模拟一下基础版本 

上图是原先数组,现在我们要求F[10](以第10项为结尾(就是7)的最大上升子序列长度)

如上图,很多项都能推第10项

但这样的话会很慢,因为每一项都会有这么多项可以推,枚举起来效率实在低,那怎样才能减少要枚举的次数呢?

其实在这几项中有许多没有枚举意义的项就拿第一项(3),第五项(2),第六项(3)举例

 

这三项中,F数组值都是1,就是说以它们为结尾的最长上升子序列长度都是1,那么对于第十项(7)我们只选择有最优的。很容易看出来,第五项(2)才是最优的,因为在这三项中,2是数值最小的不管怎么样,都比第一项和第六项(3)强(因为是要得到最长“上升”子序列,当然是前面数越小对后面越好啊),所以对于F数组值为1的,我们只选择2。

 

 

那么,我们把这个操作推广,就会得到:对于F数组值相同的几项中我们选择数值最小的,其他的全部忽略。

所以现在需要一个数组g来存储这些“有用”的项。

假设我们已经有数组g了:

(已经按F数组值从小到大排好了)

现在我们就是要找到这四个数中两边数值刚好能“夹住”7的位置

long long a[100001];
int f[100001];
//f[i]表示以第i项结尾的最长上升子序列
int g[100001];
//g[i]表示上升子序列长度为i时,结尾最小值
int sz = 0;//表示g数组有值的个数,即要遍历的个数
int maxn = 0;
	for(int i = 1;i <= n;i++)
	{
		int pos = 1;
		while(pos <= sz && g[pos] < a[i]) pos++;
		f[i] = pos;
		g[pos] = a[i];
		sz = max(sz,pos);
		maxn = max(maxn,f[i]);
	}

在代码中,我们用一个pos来实现:首先pos=1初始化,接下来那个while来循环找到位置

循环条件:pos要在这个这个g数组中的某一个位置(就是其中的因为g数组中目前有值的个数是sz)并且还需要有g数组中的第pos个要小于当前这个数a[ i ]

那循环完后就找到pos了,直接将F[ i ]赋值为pos(注意,不是pos+1,因为在循环中,pos最后会多加一个1),那么我们现在要做的就是改变g[pos]

举刚才那个例子就是说把9划掉,换成7,因为7和9的F数组值相同,最小的必定要好

sz = max(sz,pos);

 对于这行代码:

因为pos有可能比sz大,(循环中不停+1嘛)所以sz值也就要更新了,如果pos大于sz的话那么就sz=pos赋值,所以就是这行代码。

最后我把最终代码附上来

代码

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
 
long long a[100001];
int f[100001];
//f[i]表示以第i项结尾的最长上升子序列
int g[100001];
//g[i]表示上升子序列长度为i时,结尾最小值
int sz = 0;//表示g数组有值的个数,即要遍历的个数
int main()
{
	int n;
	cin >> n;
	for(int i = 1;i <= n;i++)
	{
		cin >> a[i];
	}
	int maxn = 0;
	for(int i = 1;i <= n;i++)
	{
		int pos = 1;
		while(pos <= sz && g[pos] < a[i]) pos++;
		f[i] = pos;
		g[pos] = a[i];
		sz = max(sz,pos);
		maxn = max(maxn,f[i]);
	}
	cout << n - maxn;
	return 0;
}

 

举报

相关推荐

0 条评论