题目描述:
五一到了,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;
}