【学习】线段树和并

(摘自 黄嘉泰 的课件)
##合并:
由线段树的定义我们不难写出下面的过程,来合并两棵代表范围相同的线段树

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 <cstdio>
#include <iostream>
#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;
}
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值