1471 - Defense Lines(二分查找)

4 篇文章 0 订阅

http://blog.csdn.net/Wiking__acm/article/details/8903103

After the last war devastated your country, you - as the king of the land of Ardenia - decided it was high time to improve the defense of your capital city. A part of your fortification is a line of mage towers, starting near the city and continuing to the northern woods. Your advisors determined that the quality of the defense depended only on one factor: the length of a longest contiguous tower sequence of increasing heights. (They gave you a lengthy explanation, but the only thing you understood was that it had something to do with firing energy bolts at enemy forces).

After some hard negotiations, it appeared that building new towers is out of question. Mages of Ardenia have agreed to demolish some of their towers, though. You may demolish arbitrary number of towers, but the mages enforced one condition: these towers have to be consecutive.

For example, if the heights of towers were, respectively, 5, 3, 4, 9, 2, 8, 6, 7, 1, then by demolishing towers of heights 9, 2, and 8, the longest increasing sequence of consecutive towers is 3, 4, 6, 7.

Input

The input contains several test cases. The first line of the input contains a positive integer  Z$ \le$25 , denoting the number of test cases. Then  Z  test cases follow, each conforming to the format described below.

The input instance consists of two lines. The first one contains one positive integer n$ \le$2 . 105denoting the number of towers. The second line contains n positive integers not larger than 109separated by single spaces being the heights of the towers.

Output

For each test case, your program has to write an output conforming to the format described below.

You should output one line containing the length of a longest increasing sequence of consecutive towers, achievable by demolishing some consecutive towers or no tower at all.

Sample Input

2 
9 
5 3 4 9 2 8 6 7 1 
7 
1 2 3 10 4 5 6

Output

4 
6

首先纠正一处书上翻译遗漏,可以不删除任何子序列
这道题是要求两个连续子序列拼出来的最大值。最简单的想法是以O(n)的复杂度预处理出,以i点为最右端的最长连续上升序列的长度 left[i]和i点为最左端的right[i]
然后两两拼接,这样处理的复杂度是n^2,从数据范围来看,我们是需要优化到nlogn的,怎么优化呢?
预处理是已经不能优化了,然后想对于每个i,在找在他前面能和它拼接的串的时候我们需要一一枚举吗?
我们回顾一下求最长上升子序列的nlogn算法,它在找能和第i个点拼接的方式是二分搜索:
就是用d[i]表示以i结尾的最长上升序列的长度,g[i]表示d值为i的最小最小状态编号,可以推出g是单调递增的,所以可以二分搜索。
对于这道题,是求连续上升序列,还可以用上面的方法吗?可以,因为这题也可以满足上面二分搜索的前提,那么关键就是这道题的g值怎么维护了
其实这个g值的维护,比最长上升序列的那个g值的维护简单,但感觉这里表述起来比较麻烦,用代码说明吧
if(a[i] < g[left[i]]) g[left[i]] = a[i];

这题算作LIS问题的变形,关键是需要理解LIS的nlogn解法的那个二分搜索,然后灵活运用到这个题中。

#include <bits/stdc++.h>
using namespace std;
#define maxN 200000+5
#define INF 2<<29
int lefts[maxN],rights[maxN],dp[maxN],a[maxN],d[maxN];
int main()
{
    int T,n;
    scanf("%d",&T);

    while(T--)
    {
        memset(lefts,0,sizeof(lefts));//存储最长连续字段和
        memset(rights,0,sizeof(rights));//存储答案
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            {
                scanf("%d",&a[i]);
                if(a[i]>a[i-1])
                lefts[i]=lefts[i-1]+1;
                else
                    lefts[i]=1;
            }
              rights[n]=1;
            for(int j=n-1;j>=1;j--)
            {
                if(a[j]<a[j+1])
                    rights[j]=rights[j+1]+1;//j以右的最长连续上升序列
                else
                    rights[j]=1;
            }

            int ans=-1;//接下来找到第i个之前的最长连续上升序列,在logn时间内实现
           fill(dp+1,dp+n+1,INF);//dp[i]表示长度为i的时候序列结尾的最小a值
           for(int i=1;i<=n;i++)
           {
               if(a[i] < dp[lefts[i]])
                dp[lefts[i]] = a[i];

               int k=lower_bound(dp+1,dp+n+1,a[i])-dp;//找到大于等于a[i]的第一个数
               d[i]=k+rights[i]-1;//以a[i]作为连接点的最长上升序列
               ans=max(ans,d[i]);


           }
        printf("%d\n",ans);

    }
    return 0;
}
书上的方法:

#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;
}














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值