1471 - Defense Lines(二分查找)

这道题非常巧妙,其中的思想很重要。。 题目要求删除一个连续子序列,将剩下的序列拼接起来之后问最长连续递增子序列的长度。

最长子序列的一个常见优化就是用一个数组保存到该点为止的连续子序列长度,但是对于本题来说这样的优化显然还是不够的因为n很大,二重循环还是会超时。所以我们可以只枚举一个量,但是另一个量怎么找呢, 答案是构造一个二元组,a和g,使这两个元素都严格递增。这样我们在枚举一个i时只要二分找到第一个比a[i]小的值,他对应的那个g也是最大的。

然后问题的关键就在与对集合的操作,说白了就是构造两个严格递增子序列,因此对于集合的维护是这样的: 如果这个数的前一个数的g>=它,那么把它放进去是不妥的。 反之就要将它放进集合,这样一来就要检查他之后的数的g是不是>它。

代码如下:

#include<bits/stdc++.h>
using namespace std;
int T,n,a[200005],f[200005],g[200005];
struct node {
    int a,g;
    node(const int a=0,const int g=0) : a(a),g(g) {}
    bool operator < (const node& b) const {
        return a < b.a;
    }
};

int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i=0;i<n;i++) scanf("%d",&a[i]);
        if(n==1) { printf("1\n"); continue; }
        g[0] = 1;
        for(int i=1;i<n;i++){
            if(a[i]>a[i-1]) g[i] = g[i-1]+1;
            else g[i] = 1;
        }
        f[n-1] = 1;
        for(int i=n-2;i>=0;i--){
            if(a[i]<a[i+1]) f[i] = f[i+1]+1;
            else f[i] = 1;
        }
        set<node> G;
        G.insert(node(a[0],g[0]));
        int ans = 1;
        set<node> :: iterator it;
        for(int i=1;i<n;i++) {
            bool ok = true;
            node v = node(a[i],g[i]);
            it = G.lower_bound(v);
            if(it!=G.begin()) {
                --it;
                int len = it->g + f[i];
                ans = max(ans,len);
                if(it->g>=g[i]) ok = false;
            }
            if(ok) {
                G.erase(v);
                G.insert(v);
                it = G.find(v);
                it++;
                while(it!=G.end()&&it->g<=v.g) G.erase(it++);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}












你可能感兴趣的:(二分查找,ACM,uva)