天天看点

Acwing_1014登山【最长上升子序列模型】

题目描述:

五一到了,ACM队组织大家去登山观光,队员们发现山上一个有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。

同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。

队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?

输入格式

第一行包含整数N,表示景点数量。

第二行包含N个整数,表示每个景点的海拔。

输出格式

输出一个整数,表示最多能浏览的景点数。

数据范围

2≤N≤1000

输入样例:

8
   186 186 150 200 160 130 197 220
           

输出样例:

4
           

思路分析:

  • 本题题意是按顺序浏览景点,不连续浏览两个海拔相同的景点。且一旦下山就不再上山了,这意味着访问景点的顺序是个单峰序列,即先向高处景点行走,之后再向低处景点行走。问题的关键在于这个峰值是哪点,设dp[i]表示以i点为峰值的单峰子序列的最大值,即dp[i]等于以i为末尾的最长上升子序列的长度加上以i为开头的最长下降子序列的长度。如果用一重循环取枚举峰值点i,然后再用两层循环取求以i点为分界线的最长上升子序列长度和最长下降子序列长度,时间复杂度将是立方级别的。不妨预处理下以h[i]为末尾的LIS长度以及以h[i]为开头的最长下降子序列的长度。如果我们g[i]表示以h[i]为结尾的最长下降子序列长度,那么我们最终要使用的是以h[i]为开头的最长下降子序列长度,不太方便,故设g[i]为以h[i]为开头的最长下降子序列长度,在怪盗基德的滑翔翼问题中,我们逆向的求LIS长度求得的实际就是以h[i]为开头的最长下降子序列长度,故只需要逆向遍历下h[i],求得的就是以h[i]为开头的最长下降子序列长度。
  • 总结下就是f[i]表示以h[i]为末尾的LIS长度,g[i]表示以h[i]为开头的最长下降子序列长度。f[i] = max(f[i],f[j] + 1),j < i且h[i] > h[j];g[i] = max(g[i],g[j] + 1),j > i且h[i] > h[j]。这里要注意的是不论是正向求LIS,还是逆向求LIS都是h[i] > h[j],因为方向本题,i与j的大小关系也不同。最后dp[i] = f[i] + g[i] - 1。因为峰值点h[i]被重复统计了,所以需要减一。

AC代码:

//最长上升子序列模型
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1005;

int g[N], f[N];
int a[N];
int n;

int main()
{
	scanf("%d", &n);

	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);

	int res = 0;
	for (int i = 1; i <= n; i++)
	{
		f[i] = 1;
		for (int j = 1; j < i; j++)
		{
			if (a[i] > a[j]) f[i] = max(f[i], f[j] + 1);
		}
	}

	for (int i = n; i>=1; i--)
	{
		g[i] = 1;
		for (int j = n; j > i; j--)
		{
			if (a[i] > a[j]) g[i] = max(g[i], g[j] + 1);
		}
	}

	for (int i = 1; i <= n; i++) res = max(res, f[i] + g[i] - 1);

	printf("%d\n", res);

	system("pause");
	return 0;
}
           

继续阅读