线段树(马思博)

P9478 [NOI2023] 方格染色

考虑前 95pts,显然横线和竖线就是简单扫描线,斜线因为只有五条所以直接暴力就行。

最后五分,值域变成了 \(10^9\),这对于前面的扫描线自然不在话下,对于斜线其实依然是同理的,我们只需减去和直线相交的部分即可。用 map 判断是否有删重的位置即可。之后就是扫描线板子。

CF983E NN country / Страна NN

我们可以预处理出每个点沿同一条路径可以向上跳到的深度最小的点。对于一组询问 \(u,v\),我们只需让 \(u,v\) 各自向上沿着刚才预处理出的点跳到其 LCA,然后统计步数即可。

注意到一个问题,那就是如果有一条路线跨过了 LCA,我们就会算重。考虑如何处理这样的问题。发现如果设 \(u,v\) 在跳到 LCA 的前一步时位于 \(u',v'\),那么满足条件的路线的两端一定是各自位于 \(u'\)\(v'\) 的子树内。也就是说,这种路线的两端位于 DFS 序的两个区间。不难发现是一个二维数点问题,主席树做即可。

最后,发现暴力跳会超时,于是倍增跳就行了。

CF246E Blood Cousins Return / Братья по крови возвращаются

正解是线段树合并,梳理一下是怎么想到的这个过程。

发现思维瓶颈在于,如果树剖线段树,同一深度的点的 DFS 序不连续;如果直接按照深度建线段树,无法保证只查询某一点子树内。

其实第二种思路是正确的,我们对每个节点按照深度建线段树,然后从底向上合并,这样当前点的线段树就只有子树内的点,然后直接查询即可。

P5298 [PKUWC2018] Minimax

首先肯定离散化权值。发现最终要求的这个答案式子中,\(i\)\(V_i\) 都是好求的,我们只需求出概率即可。

考虑 DP 概率。设 \(f(i,j)\) 表示节点 \(i\) 的权值为 \(j\) 的概率。显然对于叶子节点有 \(f(u,w_u)=1\),其他节点可以推出 DP 方程为

\[f(i,j)=f(l,j)\left(p_i\sum_{k=1}^{j-1}f(r,k)+(1-p_i)\sum_{k=j+1}^Vf(r,k)\right)+f(r,j)\left(p_i\sum_{k=1}^{j-1}f(l,k)+(1-p_i)\sum_{k=j+1}^Vf(l,k)\right) \]

其中 \(l,r\) 分别是节点 \(i\) 的左右儿子,\(V\) 是颜色总数。当然这是建立在节点 \(i\) 同时拥有左右儿子的情况下,如果只有左儿子,那么 DP 数组就是全面继承左儿子的。

时间和空间都无法接受。观察这个式子发现,\(p_i\) 是定值,而转移所需的是左右儿子 DP 数组的一段前缀和一段后缀。又因为一棵子树中能存在的颜色数是有限的,根据经验,我们可以通过线段树合并优化转移的过程。

具体而言,对每一个节点开一棵范围为 \([1,V]\) 的动态开点线段树,存储 \(f(i,j)\) 的值并维护区间和。由上文可得,如果一个节点只有左儿子,只需全面继承即可,那么对于左右儿子俱备的情况,就需要合并左右儿子的线段树。合并过程中,需要采用分治的思想,即对于当前两棵树的根都存在的情况,我们不做处理,而是递归然后 pushup 即可统计出答案。反之对于只有一棵树的根存在的情况,就给当前树打上一个乘法标记,这个乘法标记就是在递归过程中统计的前缀和与后缀和。思路比较巧妙,可以结合代码理解。同时这里的实现因人而异,注意时刻保持思路清晰,对着式子打代码就不容易出错。

最终合并完成后,只需对根节点的线段树扫一遍即可统计出答案。

复杂度是线段树合并的 \(O(n\log n)\)

posted @ 2025-09-06 16:05  Laoshan_PLUS  阅读(12)  评论(0)    收藏  举报