【模板】线段树合并

这是线段树的最后一个知识点啦,终于不用继续练码量超大的线段树了,因为笔者已经掌握了~

例题:n只奶牛构成了一个树形的公司,每个奶牛有一个能力值pi,1号奶牛为树根。
问对于每个奶牛来说,它的子树中有几个能力值比它大的。

solution:
做法是每个点开一棵线段树,插入这个结点上的数,根节点就为这个点的序号。如果能把一个结点的线段树的信息在合理的时间内全部插入(或者说「合并」)到它父亲的线段树中,就可以由下往上做一遍树上递推,这样每个结点的线段树都存储的是整棵子树的信息了。

先放代码吧:

int merge(int p,int q,int l,int r) {
	if(!p) return q;
	if(!q) return p;
	if(l==r) {
		t[p].sum+=t[q].sum;
		return p;
	}
	int mid=(l+r)>>1;
	t[p].lson=merge(t[p].lson,t[q].lson,l,mid);
	t[p].rson=merge(t[p].rson,t[q].rson,mid+1,r);
//	t[p].sum=t[t[p].lson].sum+t[t[p].rson].sum;
    t[p].sum+=t[q].sum;
    //they are the same
	return p;
}

这个代码还是相当好理解的,无需赘述。下面来证明一下复杂度。(我想了一中午终于想出一个超简洁的证明 —— 本来以为会很复杂的)

从代码中可以看出合并两棵树的复杂度约等于这两棵树 重合 的结点数。假如初始所有的线段树点数总和为 N N N 。因为只要合并两个节点,节点总个数就会少掉一个,所以复杂度应该是 O ( N ) O ( N ) O(N) 的。

所以不是所有情况下的线段树合并都是 O ( n l o g ⁡ n ) O ( n log ⁡ n ) O(nlogn) 的。

一般情况下我们只会对每个节点开动态开一条链,所以 N = n l o g ⁡ n N = n log ⁡ n N=nlogn ,复杂度才会是 O ( n l o g ⁡ n ) O ( n log ⁡ n ) O(nlogn)

哇怎么这么短就写完了 ……

update:「abc183F」Confluence
题意:n个人,每个人属于一个班级ci,这些人会有些小团体(并查集)

两种操作:

1 a b,将a所在的集体和b所在的集体合并

2 x y,问在x的集体中有多少人在y班

第一种做法启发式合并,每次将点数较少的接到点数较多的上面。具体可以用 m a p map map之类的 S T L STL STL来存集合。

第二种方法是线段树合并。我们把一个点所含的种类 [ 1 , 2 e 5 ] [1,2e5] [1,2e5] 看成一个树的结构,时间复杂度是严格 O ( n l o g n ) O(nlogn) O(nlogn) 的。

这个想法很妙。我们把一个线性的单点合并放到动态开点线段树上,这样每次合并一个点都删除了一个点,保证了算法的高效。

第二种算法的时间复杂度显然是优于第一种的,第一种做法只能“单个搬运”,导致合并代价是不稳定的。而第二种则把若干子树进行“整体搬运”,我们也通过理论证明了它时间上界和优秀的搬运性质(我们发现它在整个合并过程中没有新建点)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值