關於線段樹的那些奇技淫巧

目錄:我在右邊
如果你不會線段樹,戳這裏

維護區間max/min值:

這就是push_up()淺顯易懂.

void push_up(int rt) {
    tree[rt].max = max(tree[lson].max, tree[rson].max);
    tree[rt].min = min(tree[lson].min, tree[rson].min);
}

建樹的時候就那樣建,push_down的時候看一下max和min都改成lazy就行了.
有的時候用不到push_down();

區間查詢max/min值

也很好懂,和求區間和差不多.
有修改操作的時候可以加上pushd_down();

int query_max(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return tree[rt].max;
    int mid = (l + r) >> 1, maxn = -1;
    if (L <= mid) maxn = max(maxn, query_max(lson, l, mid, L, R));
    if (R > mid) maxn = max(maxn, query_max(rson, mid + 1, r, L, R));
    return maxn;
}

維護區間和+區間開平方.

這是個好題
強烈推薦GSS毒瘤數據結構,在洛谷可以搜到,做完之後會感覺自己的思維得到了昇華.
A一題提神醒腦,A倆題永不疲勞,A仨題長生不老
因爲一個大整數\(\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{n}}}}}} = 1\)我們可以用lazy判斷一下這個區間開了幾次,如果大於等於6的話就沒必要搞了(然而我沒寫)
而且開方的時候判斷一下區間的最大值是不是1,如果區間的最大值都是1了,那麼其他的必然也是1,就沒有必要在開方了.

關於更新:

void update(int L, int R, int l, int r, int rt) {
    if (l == r) {
        t[rt].sum = sqrt(t[rt].sum);
        t[rt].mx = sqrt(t[rt].mx);
        return ;
    }
    int m = (l + r) >> 1;
    if (L <= m && t[lson].mx > 1) update(L, R, l, m, lson);
    if (R > m && t[rson].mx > 1) update(L, R, m + 1, r, rson);
    pushup(rt);
}

查詢就不用說的吧,和區間求最大值一樣啊。

區間最大子段和.

好題 可以說是裸題.
難點在於push_up和query

push_up

void push_up(int rt) {
    tree[rt].sum = tree[lson].sum + tree[rson].sum;//這就是普通的區間和
    
    tree[rt].qian = max(tree[lson].qian, tree[lson].sum + tree[rson].qian);
    tree[rt].hou = max(tree[rson].hou, tree[rson].sum + tree[lson].hou);
    //區間的前綴和的最大值就是左區間的前綴和和(右區間的全綴合加上左區間的和)取max
    //區間後綴和類比可得
    tree[rt].zi = max(tree[lson].zi, tree[rson].zi);
    tree[rt].zi = max(tree[rt].zi, tree[lson].hou + tree[rson].qian);
    //最大子段和就是左右區間最大子段和,還有左區間後綴和加上右區間前綴和取max
}

關於query:

node unioc(node a, node b) {
    node ans;//區間合併可類比push_up
    ans.sum = a.sum + b.sum;
    ans.zi = max(a.zi, b.zi);
    ans.zi = max(a.hou + b.qian, ans.zi);
    ans.qian = max(a.qian, a.sum + b.qian);
    ans.hou = max(b.hou, b.sum + a.hou);
    return ans;
}

node query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return tree[rt];
    int mid = (l + r) >> 1;
    if (L > mid) return query(rson, mid + 1, r, L, R);//如果這個區間都在右邊,直接在右邊算就行
    if (R <= mid) return query(lson, l, mid, L, R);//爭端區間都在左邊
    return unioc(query(lson, l, mid, L, R), query(rson, mid + 1, r, L, R));//如果兩端都有那麼還要合併區間.
}

加上區間修改就變成了這個題
也挺簡單的,只要你會了上邊那個.
區間修改也挺簡單的.通俗易懂。

void change(int rt, int c, int l, int r, int pos) {
    if (l == r) {
        tree[rt].sum = tree[rt].ls = tree[rt].rs = tree[rt].mis = c;
        return;
    }
    int mid = (l + r) >> 1;
    if (pos <= mid) change(lson, c, l, mid, pos);
    if (pos > mid) change(rson, c, mid + 1, r, pos);
    push_up(rt);
}

最快樂的當然是樹剖+線段樹

那上邊那些,只要是樹剖不是特別難,很用得着.
但是還有那些不在認知範圍內的一些東西.
比如這個:

P 2186 染色.

求的是連續的顏色段的個數.
因爲給出的111221的顏色段爲三.
我們考慮怎麼從下邊上傳.
如果我們是從111 221 來的,因爲中間沒有相同的可以接加上左右兒子的顏色段.
但是如果是這樣11 12 那麼合併的時候需要判斷一下左右兒子相連 的那一部分是不是相同.
如果相同那麼需要-1,不相同則不用-1.然後我們就能寫出push_up 啦.

push_up

obviously

void push_up(int rt) {
        if (tree[lson].rc == tree[rson].lc) 
            tree[rt].num = tree[lson].num + tree[rson].num - 1;
        else tree[rt].num = tree[lson].num + tree[rson].num;
        tree[rt].lc = tree[lson].lc, tree[rt].rc = tree[rson].rc;
    }

因爲區間查詢的時候也需要上傳,那麼我們就需要判斷所求區間是不是需要合併一下子,
如果需要合併那麼還要判斷是不是需要-1的那種情況.
那麼我們query也有了.

query

obviously

int query(int rt, int l, int r, int L, int R) {
        if (L <= l && r <= R) return tree[rt].num;
        push_down(rt);
        int mid = (l + r) >> 1; int ans = 0;
        if (L > mid) return query(rson, l, r, L, R);
        else if (R <= mid) return query(lson, l, r, L, R);
        else {
            ans = query(lson, l, mid, L, R) + query(rson, mid + 1, r, L, R);
            if (tree[lson].rc == tree[rson].lc) ans--;
            return ans;
        }
    }

總結(思考方向):

以後做線段樹的時候考慮怎麼上傳,怎麼下放,區間查詢的時候需不需要合併.
合併的時候需要注意什麼問題.

【未完待續】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章