线段树合并及训练12题题解

权值线段树

就是把线段树当桶。可以解决全局第k小,求一个数的前驱和后继等。

动态开点线段树

就是只在修改的时候新建节点。如果是要pushdown,一般情况下就是在pushdown里面新建节点。但也有特殊情况,见下文。

线段树合并

其实是动态开点权值线段树合并。
一般用于解决序列叠加问题。其时间复杂度基本上是合并的两棵树中重复的节点个数。大概是\(O(n \log n)\)的。
下面是代码:

点击查看代码
int merge(int p, int q, int l, int r)
{
	if(!p)return q;
	if(!q)return p;
	if(l == r)
	{
		tr[p].num += tr[q].num;
		return p;
	}
	int mid = (l + r) >> 1;
	lsp = merge(lsp, lsq, l, mid);
	rsp = merge(rsp, rsq, mid + 1, r);
	pushup(p);
	return p; 
}

A.普通平衡树

修改是简单的。对于查询,写三个函数:
1.查询一个数的排名。
2.查询排名为k的数。
3.查询小于等于k的数的个数。
用这三个就能凑出来所有的查询操作。

B.[bzoj3307]雨天的尾巴

先树上差分,给u+1,v+1,lca-1,fa[lca]-1。
接下来线段树从下往上合并,维护一个max以及其对应的下标即可。

C.bzoj4636: 韶身的数列

可以这么做:先给这些操作按权值从小到大排序,然后维护线段树的区间推平懒标记。
注意出数据人不讲武德,要判断一下l==r的情况,由于左闭右开所以会寄。

D.[POI 2011] ROT-Tree Rotations

是这样的,在线段树合并的过程中,可以计算出调转或不调转的代价。我们先计算这一层的贡献:
如果不调转,就需要加上tr[rsp].num * tr[lsq].num,否则就是tr[lsp].num * tr[rsq].num
遇到这种难以直接计算的东西,就要考虑贡献思想

E.[Usaco2017 Jan]Promotion Counting

线段树合并板子题,不提。

F.bzoj4399: 魔法少女LJJ

首先,我认为本题5操作说的不清晰,应为查询第k小的数。 如果是查权值应该是去重后的数中的第k小。
对于操作1、2、5、7,就是板子。
对于6,有一个技巧,就是\(\log_a(mn) = \log_a(m) + \log_a(n)\),维护一个元素的对数和。
对于3和4,考虑打一个清除的标记,然后注意下放标记时不要新建子节点。
还有注意,线段树合并时记得判断两个点是否已经在同一个集合里面。

G.[Lydsy1706月赛]大根堆

本题是一道线段树合并优化DP。
首先写出来一个暴力DP:
\(f_{u,i}\)为以u为根的子树之中,选中的点之中的最大值不超过i时的最多点数。
可以使用离散化,优化一下。
接下来发现,转移分两类:
1.\(f_{u,i} = \sum f_{v,i}\) \(\space \space\)其中v是u的儿子。
2.当\(i > val[u]\)时, \(f_{u,i} = max(f_{u,i},f_{u,val[u] - 1} + 1)\)
先转移1再转移2,第一个是不选u,第二个是选u。
容易发现,1很适合线段树合并优化。并且根据这两个式子,\(f_u\)单调不降。
所以对于2,可以选择去二分一段区间\([val[u], r]\)使得其中的i满足\(f_{u,val[u] - 1} \le f_{u,i} < f_{u,val - 1} + 1\),中间的被一夹,发现其一定全都等于\(f_{u,val[u] - 1}\),所以在线段树上对其+1即可。需要的是区间修改,单点查询的线段树。

这里有一个东西:

标记永久化

就是给每个节点维护一个sum,所有被完全覆盖的区间都会被+=val,然后查询的时候把每层的sum加起来即可。

如果要维护区间和,那么就是修改时所有包含这个修改区间的区间的sum都会被+=val(R-L+1),查询时还是一路+(tag(R-L+1)),这样就不需要pushup和pushdown。

H.「FJOI2018」领导集团问题

本题和上一题基本一样,注意一些细节:
\(f_{u,i}\)为以u为根的子树之中,选中的点之中的最小值不小于i时的最多点数。
1.\(f_{u,i} = \sum f_{v,i}\) \(\space \space\)其中v是u的儿子。
2.当\(i \le val[u]\)时, \(f_{u,i} = max(f_{u,i},f_{u,val[u]} + 1)\)
注意此时f是单调不增的,可以从式子的角度理解,所以二分要稍微改一下。
或者,感性理解,选取最小值比1大的数肯定比最小值比m大的容易。

I.「BJOI2014」大融合

我没有使用线段树合并。
首先可以离线,先把树建出来,这样方便。
可以使用树上差分+dfs序+树状数组来解决该问题,每次给从接上去的节点到根加上目前该节点子树大小,再给根的父亲减掉。注意一下初始化,给自己+1,给自己的父节点-1。

J.「NOIP2016」天天爱跑步

基本上是自己做出来的,非常强大。
首先可以把路径按照LCA拆解开来,然后发现树链加不是加同一个数,但是加的是一个等差数列。所以考虑给往上走的一段加上其所在点深度,往下走的减去,这样就是一个数了,非常好。
然后按照雨天的尾巴的思路树上差分。可以选择开上两棵线段树,分别处理向上的和向下的。

K.[ZJOI2010]基站选址

非常高质量的一道线段树优化DP。
首先考虑暴力DP:
\(f_{i,j}\)为到了i放了j个基站,且钦定在i放了基站的最小花费。
方程:\(f_{i,j} = min\{f_{k,j-1}+cost(k,i)\}+c_i\)
\(cost(k,i)\)是k到i之间,两头有基站,中间要求赔偿的费用和。
这个东西是可以\(O(n^3)\)计算的,不太能优化。

考虑如何优化,可以对于每一轮j,建一棵线段树,然后把上一轮f的结果放进去,查询就非常简单了,维护一个区间min即可。考虑如何修改,可以把贡献拆开加进去。你可以预处理每一个点左边和右边的第一个能覆盖到它的点\(L[i]\)\(R[i]\)。我们让i这个点作为\(R[k]\),然后存储对应的k,很显然,在往后的i+1等一定覆盖不到k,所以一定要交k的赔款,我们把这些赔款加给\([1,L[k] - 1]\),这些点是前面覆盖不到k的。

最后加个技巧,加一个点n+1,然后让它的d和w都是inf,c是0,这样可以统计所有到n建/不建的最小值。

L.[HNOI2012]永无乡

板子题,使用线段树合并配合并查集解决。波波瞎指挥,乱调题单顺序。

posted @ 2025-03-09 20:25  The_Wandering_Earth  阅读(51)  评论(0)    收藏  举报
/