1. 程式人生 > 實用技巧 >Luogu SP1043 GSS1 - Can you answer these queries I

Luogu SP1043 GSS1 - Can you answer these queries I

思路

首先說明一下這道題的重難點。這道題由於沒有更新操作,所以我們就省去了update和維護lazytag的麻煩,但是不要高興的太早。這道題要求我們求區間最大子段和,這個東西顯然很不好維護。

要維護它,我們先要看一下在查詢和建樹過程中可能遇到的情況。

一、該區間的最大子段和完全被包括在它的左兒子中(如圖):

這樣我們就可以把左兒子的最大子段和作為候選值加入。

二、該區間的最大子段和完全被包括在它的右兒子中(如圖):

這樣我們就可以把右兒子的最大子段和作為候選值加入。

三、該區間的最大子段和在左兒子和右兒子都有一部分(如圖):

這種情況比上兩種稍稍複雜一點,仔細想一下,我們就可以發現,這種情況下的最大子段和即為左兒子的最大字尾和加上右兒子的最大字首和。為什麼這樣是正確的呢?因為我們要求最大子段和,

最大子段和的定義就是在一個序列中的所有子串中的和最大的那個(我有點說不清楚QWQ,但應該很好理解)。說白了,我們要讓一個點所在的區間長度最大,就要讓這個點兩邊的區間長度都最大

(有點類似於貪心的感覺)。把這個值作為候選值加入。

這樣的話,我們就有了維護區間最大子段和的基本方法。我們對上述三種情況歲統計出來的值全都取一個max(別告訴我你對三個數取max還要排序,雖然我剛學的時候也是這麼做的),然後就可以得出

這個區間的最大子段和。

解決了上述去最大子段和的問題,我們又引申出來了兩個新問題,那就是怎樣維護區間最大字首和和區間最大字尾和。由於這兩個東西的維護方法非常類似(同時也是因為我懶),我就只給出區間最大

字首和的維護方法。

同樣是分情況討論:

一、區間最大字首和被完全包括在該區間的左兒子中(如圖):

這樣我們就可以把左兒子的最大字首和作為候選值加入。

二、區間最大字首和在該區間的左兒子和右兒子都有一部分(如圖):

這樣我們就可以看出,此時的最大字首和就可以表示為左兒子的區間和加右兒子的最大字首和,並把這個值作為候選值加入。最後只要對上述兩個值取個max就行……

區間最大字尾和和區間最大字首和的維護方法幾乎一毛一樣,就是把區間最大字首和翻轉了一下而已,所以我覺得沒有必要寫了,畢竟也沒啥不好理解的。

再就是在查詢的時候對上面說的求最大子段和的方法中提到的三個值分別分類討論一下(千萬不要照著線段樹模板的query寫啊)。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define MAXN 50010
typedef long long ll;
int n, m, a[MAXN];
struct node{
    ll val_f, val_b;//val_f維護區間最大字首和,val_b維護區間最大字尾和
    ll val_sum;//區間和
    ll val_mal;//區間最大子段和
} tree[MAXN << 2];
inline int lson(int k) { return k << 1; }
inline int rson(int k) { return k << 1 | 1; }
inline void push_up(int k){//注意!pushup是重點!!!
    tree[k].val_sum = tree[lson(k)].val_sum + tree[rson(k)].val_sum;//更新區間和
    tree[k].val_f = std::max(tree[lson(k)].val_sum + tree[rson(k)].val_f, tree[lson(k)].val_f);//更新區間最大字首和
    tree[k].val_b = std::max(tree[rson(k)].val_sum + tree[lson(k)].val_b, tree[rson(k)].val_b);//更新區間最大字尾和
    tree[k].val_mal = std::max(tree[lson(k)].val_b + tree[rson(k)].val_f, std::max(tree[lson(k)].val_mal, tree[rson(k)].val_mal));
    //更新區間最大子串和,上述式子不明白的去看上面的推導
    return;
}
void build(int k,int l,int r){
    if(l==r){
        tree[k].val_sum = a[l];
        tree[k].val_f = a[l];
        tree[k].val_b = a[l];
        tree[k].val_mal = a[l];
        return;
    }//這些應該都沒啥疑問了
    int mid = (l + r) >> 1;
    build(lson(k), l, mid);
    build(rson(k), mid + 1, r);
    push_up(k);
}
node query(int k,int ql,int qr,int l,int r){//這裡query的返回值定義為node,是為了更方便地查詢和分類討論
    if(ql<=l&&r<=qr)//這也就是為什麼這道題寫結構體線段樹會好一點
        return tree[k];
    int mid = (l + r) >> 1;
    if(qr<=mid)//分類討論1:區間全部在左子樹
        return query(lson(k), ql, qr, l, mid);
    if(ql>mid)//分類討論2:區間全部在右子樹
        return query(rson(k), ql, qr, mid + 1, r);
    node ls, rs, res;//分類討論3:區間在左右子樹都有
    ls = query(lson(k), ql, qr, l, mid);
    rs = query(rson(k), ql, qr, mid + 1, r);
    res.val_sum = ls.val_sum + rs.val_sum;
    res.val_f = std::max(ls.val_f, ls.val_sum + rs.val_f);
    res.val_b = std::max(rs.val_b, rs.val_sum + ls.val_b);
    res.val_mal = std::max(ls.val_b + rs.val_f, std::max(ls.val_mal, rs.val_mal));
    return res;//上述分類討論3的查詢過程和pushup完全相同
}
int main(){
    scanf("%d", &n);
    for (int i = 1; i <= n;++i)
        scanf("%d", &a[i]);
    build(1, 1, n);//建樹
    scanf("%d", &m);
    for (int i = 1; i <= m;++i){
        int x = 0, y = 0;
        scanf("%d%d", &x, &y);
        printf("%lld\n", query(1, x, y, 1, n).val_mal);
        //由於返回的是結構體,所以不要忘了後面的.val_mal
    }
    return 0;
}