(摘自 黄嘉泰 的课件)
##合并:
由线段树的定义我们不难写出下面的过程,来合并两棵代表范围相同的线段树
merge(a,b):
如果a,b中有一个不含任何元素,就返回另一个
如果a,b都是叶子,返回merge_leaf(a,b)
返回merge(a->l,b->l)与merge(a->r,b->r)连接成的树
由于a,b两棵树结构相同,上面的过程的正确性是显然的。
a,b中可能存在key相同的元素,我们之前对线段树的定义对这种情况无能为力,所以需要一个merge_leaf过程来给出新树中该位置的元素
为了方便确定一棵树是否为空,动态开辟节点。若某棵子树为空,则其父亲的对应指针为空。
##复杂度:
若merge_leaf过程和+运算的代价都是O(1)
容易看出合并的开销正比于两棵树公共的节点数
单次merge操作的开销可大可小,但我们可以立即得到一个非常有用的结论:
若有n棵含有单个元素的树,经过n-1次merge操作,将他们合并成一棵的代价是O(nlogn)或O(nlogU)
理由:这个过程的开销不会比向一棵空树顺序插入n个整数来的大。
##例题:
###题意:
输入一棵树和一些询问,每个询问x a b,意为询问以x为根的子树中值为a到b数字的个数。
###思路:
考虑读入所有的询问离线的处理,从深度深的开始处理,用权值线段树来维护a到b的数字个数,提前把数据离散化,每次用完直接跟子树合并并插入根节点的权值。
合并的代码:
void merge(node *a, node *b){
a->val += b->val;
if(a->ch[0] != null && b->ch[0] != null) merge(a->ch[0], b->ch[0]);
else if(a->ch[0] != null) a->ch[0] = b->ch[0];
if(a->ch[1] != null && b->ch[1] != null) merge(a->ch[1], b->ch[1]);
else if(a->ch[1] != null) a->ch[1] = b->ch[1];
}
在合并的之前一定要注意传进函数内的指针是否已经分配了内存。
##练习:
###题目来源:BZOJ 2212
###题目描述:
现在有一棵二叉树,所有非叶子节点都有两个孩子。在每个叶子节点上有一个权值(有n个叶子节点,满足这些权值为1…n的一个排列)。可以任意交换每个非叶子节点的左右孩子。
要求进行一系列交换,使得最终所有叶子节点的权值按照遍历序写出来,逆序对个数最少。
###Input
第一行n
下面每行,一个数x
如果x==0,表示这个节点非叶子节点,递归地向下读入其左孩子和右孩子的信息,
如果x!=0,表示这个节点是叶子节点,权值为x
1<=n<=200000
###Output
一行,最少逆序对个数
###Sample Input
3
0
0
3
1
2
###Sample Output
1
###思路:
不难发现逆序对的增加只可能发生在不能旋转的两个根之间,所以只需要用权值线段树来维护逆序对数,不断的向上合并就可以了,每次转成逆序对数较小的一种组合,然后合并,之后的逆序对数与此没有影响。
注意:要开long long。
###代码:
#include
#include
#define mid ((l+r)>>1)
const int maxn = 200010;
typedef long long ll;
struct node{
ll val;
node *ch[2];
}Pool[maxn*20], *root, *null;
int cnt, n;
ll ans;
node *get(){
node *now = &Pool[++ cnt];
now->val = 0;
now->ch[0] = now->ch[1] = null;
return now;
}
void merge(node *a, node *b){
a->val += b->val;
if(a->ch[0] != null && b->ch[0] != null) merge(a->ch[0], b->ch[0]);
else if(b->ch[0] != null) a->ch[0] = b->ch[0];
if(a->ch[1] != null && b->ch[1] != null) merge(a->ch[1], b->ch[1]);
else if(b->ch[1] != null) a->ch[1] = b->ch[1];
return;
}
void build(node *now, int l, int r, int val){
if(l == r) return;
int wh = 1;
if(val <= mid) wh = 0;
now->ch[wh] = get();
now->ch[wh]->val += 1;
build(now->ch[wh], wh == 0 ? l : mid+1, wh == 0 ? mid : r, val);
}
ll calc(node *a,node *b){
ll res = 0;
if(a->ch[0] != null && b->ch[1] != null) res += a->ch[0]->val * b->ch[1]->val;
if(a->ch[1] != null && b->ch[1] != null) res += calc(a->ch[1], b->ch[1]);
if(a->ch[0] != null && b->ch[0] != null) res += calc(a->ch[0], b->ch[0]);
return res;
}
node *dfs(){
int x;
node *now, *lch, *rch;
scanf("%d", &x);
if(x == 0){
lch = dfs();
rch = dfs();
ans += std::min(calc(lch, rch), calc(rch, lch));
merge(lch, rch);
return lch;
}else{
// 给now一个初始值。
build(now = get(), 1, n, x);
return now;
}
}
int main(){
null = &Pool[0];
null->val = 0;
null->ch[0] = null->ch[1] = null;
scanf("%d", &n);
root = dfs();
printf("%lld", ans);
return 0;
}