1. 程式人生 > >神奇的操作——線段樹合並(例題: BZOJ2212)

神奇的操作——線段樹合並(例題: BZOJ2212)

所有 class 例題 con ++ right algo 集合 online

什麽是線段樹合並?

首先你需要動態開點的線段樹。(對每個節點維護左兒子、右兒子、存儲的數據,然後要修改某兒子所在的區間中的數據的時候再創建該節點。)

考慮這樣一個問題:

你現在有兩棵權值線段樹(大概是用來維護一個有很多數的可重集合那種線段樹,若某節點對應區間是\([l, r]\),則它存儲的數據是集合中\(\ge l\)\(\le r\)的數的個數),現在你想把它們倆合並,得到一棵新的線段樹。你要怎麽做呢?

提供這樣一種算法(tree(x, y, z)表示一個左兒子是x、右兒子是y、數據是z的新結點):

tree *merge(int l, int r, tree *A, tree *B){
    if
(A == NULL) return B; if(B == NULL) return A; if(l == r) return new tree(NULL, NULL, A -> data + B -> data); int mid = (l + r) >> 1; return new tree(merge(l, mid, A -> ls, B -> ls), merge(mid + 1, r, A -> rs, B -> rs), A -> data + B -> data); }

(上面的代碼瞎寫的……發現自己不會LaTeX寫偽代碼,於是瞎寫了個“不偽的代碼”,沒編譯過,湊付看 ><)

這個算法的復雜度是多少呢?顯然是A、B兩棵樹重合的節點的個數。

那麽假如你手裏有m個只有一個元素的“權值線段樹”,權值範圍是\([1, n]\),想都合並起來,復雜度是多少呢?復雜度是\(O(m\log n)\)咯。

這個合並線段樹的技巧可以解決一些問題——例如這個:BZOJ 2212。

題意:

給出一棵完全二叉樹,每個葉子節點有一個權值,你可以任意交換任意節點的左右兒子,然後DFS整棵樹得到一個葉子節點組成的序列,問這個序列的逆序對最少是多少。

可以看出,一個子樹之內調換左右兒子,對子樹之外的節點沒有影響。於是可以DFS整棵樹,對於一個節點的左右兒子,如果交換後左右兒子各出一個組成的逆序對更少則交換,否則不交換。如何同時求出交換與不交換左右兒子情況下的逆序對數量?可以使用線段樹合並。

用兩個權值線段樹分別表示左右兒子中所有的數的集合。在合並兩棵線段樹的同時,A -> right_sonB -> left_son可以構成不交換左右兒子時的一些逆序對,A -> left_sonB -> right_son可以構成交換左右兒子時的一些逆序對,其余的逆序對在線段樹AB的左右子樹中,可以在遞歸合並的時候處理掉。

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define space putchar(' ')
#define enter putchar('\n')
using namespace std;
typedef long long ll;
template <class T>
void read(T &x){
    char c;
    bool op = 0;
    while(c = getchar(), c < '0' || c > '9')
        if(c == '-') op = 1;
    x = c - '0';
    while(c = getchar(), c >= '0' && c <= '9')
        x = x * 10 + c - '0';
    if(op) x = -x;
}
template <class T>
void write(T x){
    if(x < 0) putchar('-'), x = -x;
    if(x >= 10) write(x / 10);
    putchar(x % 10 + '0');
}

const int N = 10000005;
int n, tmp, ls[N], rs[N], data[N], tot;
ll ans, res1, res2;

int newtree(int l, int r, int x){
    data[++tot] = 1;
    if(l == r) return tot;
    int mid = (l + r) >> 1, node = tot;
    if(x <= mid) ls[node] = newtree(l, mid, x);
    else rs[node] = newtree(mid + 1, r, x);
    return node;
}
int merge(int l, int r, int u, int v){
    if(!u || !v) return u + v;
    if(l == r) return data[++tot] = data[u] + data[v], tot;
    int mid = (l + r) >> 1, node = ++tot;
    res1 += (ll)data[rs[u]] * data[ls[v]], res2 += (ll)data[ls[u]] * data[rs[v]];
    ls[node] = merge(l, mid, ls[u], ls[v]);
    rs[node] = merge(mid + 1, r, rs[u], rs[v]);
    data[node] = data[ls[node]] + data[rs[node]];
    return node;
}
int dfs(){
    read(tmp);
    if(tmp) return newtree(1, n, tmp);
    int node = merge(1, n, dfs(), dfs());
    ans += min(res1, res2);
    res1 = res2 = 0;
    return node;
}

int main(){
    read(n);
    dfs();
    write(ans), enter;
    return 0;
}

神奇的操作——線段樹合並(例題: BZOJ2212)