线段树合并的思想和应用(末尾附一些习题)

讲前须知:线段树动态开点

1.概念

线段树合并,一般来说是与线段树动态开点一起用的,它的作用将两棵线段树所统计的信息整合到一棵线段树上。(由于多棵线段树会导致空间复杂度极高,所以线段树合并一般会与动态开点一起使用)

2.思想

我们在合并线段树的时候,肯定不可能一个点一个点的合并,那么我们就需要将一些无用的合并操作去掉。由于我们是动态开点,那么无用的操作指的就是,两棵线段树中有一棵未开辟的节点(或两棵都未开辟),那么直接返回已经开辟了的那一棵就可以了。代码:

int merge(int u , int v)
{
	if (!u || !v)
	{
		return u + v;
	}
	int t = ++tot;//重新开点记录当前两棵树的信息
	sum[t] = sum[u] + sum[v]//根据题意,这里可以有不同的写法,有时还可能记录某些答案
	ls[t] = merge(ls[u] , ls[v]);
	rs[t] = merge(rs[u] , rs[v]);//递归处理左右子树
	return t;
}

上面这种写法适用于大多数情况,但它对于所有两棵子树共同的节点都开了一个新节点。所以当空间限制极为苛刻的时候,这种写法不一定适用。另一种写法:

void merge(int &u , int v)
{
	if (!u || !v)
	{
		u += v;
		return;
	}
	sum[u] += sum[v];
	merge(ls[u] , ls[v]);
	merge(rs[u] , rs[v]);
}

这种写法的优势是:不用重新开点,直接将u和v的信息全部合并到u上。但也因为这一点,u所保存的信息将被破坏掉,即最后无法保存合并前的某一棵线段树。所以,这种写法一般都用在树上,父亲节点合并子节点信息时。(还有,我有一次打比赛时这样写爆零了,之后怎么查都查不出来错,所以,在空间足够的情况下,建议使用前一种写法。当然,如果是我模板写的有问题,还望雅正)

3.应用

感觉我做过的大部分题都是在树上,父亲节点合并子节点信息时会用到。当然,其它很多地方应该都会用到,但我太蒻了,那些题还是交给大佬来吧。

4.习题

1.入门
板子:https://www.luogu.org/problemnew/show/P3605
2.进阶 (有一定思维难度)
https://www.luogu.org/problemnew/show/P3521 (较难)
https://www.luogu.org/problemnew/show/P3899 (做法很多)
https://www.luogu.org/problemnew/show/P3224 (较简单)
3.再进阶
http://acm.hdu.edu.cn/showproblem.php?pid=5709 (不是很好做)

你可能感兴趣的:(线段树合并的思想和应用(末尾附一些习题))