1. 程式人生 > >Defense Lines(二分查詢)

Defense Lines(二分查詢)

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.

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.

2 
9 
5 3 4 9 2 8 6 7 1 
7 
1 2 3 10 4 5 6
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;
}