神奇的操作——線段樹合並(例題: BZOJ2212)
什麽是線段樹合並?
首先你需要動態開點的線段樹。(對每個節點維護左兒子、右兒子、存儲的數據,然後要修改某兒子所在的區間中的數據的時候再創建該節點。)
考慮這樣一個問題:
你現在有兩棵權值線段樹(大概是用來維護一個有很多數的可重集合那種線段樹,若某節點對應區間是\([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_son
與B -> left_son
可以構成不交換左右兒子時的一些逆序對,A -> left_son
與B -> right_son
可以構成交換左右兒子時的一些逆序對,其余的逆序對在線段樹A
、B
的左右子樹中,可以在遞歸合並的時候處理掉。
#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)