题意:给出长度为N的序列,现在让你删除一个连续的、任意长度的序列,从而形成一个最长的上升子序列。
思路:第一遍看错题了,以为是标准的最长上升子序列,然后就错了。
再看一遍题意,其实只是让你删除一个连续的、任意长度的序列,注意只有一次机会。
这样,我们就能发现,其实最终的最长上升子序列是由两段拼成的,这两段分别是连续的。
首先第一个技巧,就是分别求出以位置i开始和结束的最长上升子序列f(i),g(i)。这个可以O(N)得到。
但是我们要是直接暴力枚举i,j, i < j 的话,复杂度为O(N^2),会超时。这样,我们就枚举i,同时去找最优的j,使f(i) + g(j)最大。
我们可以发现,同标准的最长上升子序列一样,对于f(i) = f(j),且A[i] < A[j],位置i比位置j更有可能成为最优解。这就是说,对于相同的长度,数字的值更小,更能继续延长该序列。这样,我们就能像标准的最长上升子序列一样,用单调队列进行优化。对于每个f(i),保存最小的A[i]。因为是单调的,我们就能二分找目标值。
所以整体的复杂度为:O(nlogn)
代码如下:
#include <cstdio>
#include <algorithm>
#include <set>
#include <cstring>
using namespace std;
const int MAX = 200010;
int N,T,a[MAX],f[MAX],g[MAX],b[MAX];
int main(void)
{
//freopen("input.txt","r",stdin);
scanf("%d",&T);
while(T--){
scanf("%d",&N);
for(int i = 0; i < N; ++i)
scanf("%d",a+i);
for(int i = 0; i < N; ++i)
if(i && a[i] > a[i-1])
f[i] = f[i-1] + 1;
else
f[i] = 1;
for(int i = N - 1; i >= 0; --i)
if(i + 1 < N && a[i] < a[i+1])
g[i] = g[i+1] + 1;
else
g[i] = 1;
int ans = 0;
memset(b,0x3f,sizeof(b));
b[0] = 0;
for(int i = 0; i < N; ++i){
ans = max(ans,g[i] + (int)(lower_bound(b,b+N+1,a[i]) - b - 1));
b[f[i]] = min(b[f[i]],a[i]);
}
printf("%d\n",ans);
}
return 0;
}