加载中...

线段树合并/分裂

线段树合并

pZcsV4x.png
pZcsngO.png
线段树合并,通常用于动态开点的权值线段树中。在树上统计问题中较为常见,一般形式为:对 \(n\) 个结点中的每一个都建立一棵自己的权值线段树,预处理每个结点的线段树后,再 \(dfs\) 一遍整棵树,对每个结点的所有子树进行线段树合并。(合并后,每个结点的线段树维护的是其子树内所有点的信息;若预处理是在作差分,则合并相当于恢复差分,线段树维护的是每个结点自身的实际值)

核心函数 \(merge:\)

int merge(int nl, int nr, int u1, int u2){
    if(!u1 || !u2) return u1 + u2;
    if(nl == nr){ // 叶节点的数据直接合并到一起
        sum[u1] += sum[u2];
    }
    else{ // 递归合并,最终值合并到u1上
        int mid = nl + nr >> 1;
        ls[u1] = merge(nl, mid, ls[u1], ls[u2]);
        rs[u1] = merge(mid + 1, nr, rs[u1], rs[u2]);
        pushup(u1); 
    }
    return u1;
}

P4556 雨天的尾巴

树上路径加操作可以利用树上差分优化,将路径修改转化为仅在 \(4\) 个点上修改相应的权值线段树;最后还原差分做子树合并的过程中做线段树合并。

code

P3521

一道难题,需要在线段树 merge 操作真正执行前统计左右两个子树叶节点构成序列的逆序对数量,(这里先只考虑不交换,交换即将左右两子树编号交换)取左子树的 sum[rs] 和右子树的 sum[ls] 并相乘,得到在当前子值域中对逆序对数量的贡献,再继续递归累计更小值域内的贡献。

code

线段树合并优化 dp

P4577

先将所有点权离散化,使得值域为 \([1, m]\)

状态定义

\(dp_{u,i}\):考虑以 \(u\) 为根的子树,其中选择的最小点权恰好为 \(i\),可选择的最多的点数量。

状态转移

决策子树 \(u\) 中选 \(u\) 或不选 \(u\)

  • \(u\)

\[dp_{u,a_{u}} \leftarrow \sum_{v \in son[u]} \max(dp_{v,a_{u} \backsim m}) + 1 \]

  • 不选 \(u\):假设子树 \(u\) 已经合并了一些子树,现在正在合并子树 \(v\)

\[dp_{u,i} \leftarrow dp_{u,i} + \max(dp_{v,i \backsim m})(\forall i \in [1, m]) \]

显然直接暴力转移的时间和空间复杂度都是 \(O(n^{2})\) 的。考虑用线段树合并来优化转移:

  1. 对于第一个转移,由于需要修改的只有 \(a_{u}\) 这一个单点,可以单独设计一个 update 函数来实现
  2. 对于第二个转移,相当于合并两棵线段树,可以在 merge 函数中实现。

实现细节

在 merge 中,有如下:

if(!u1 || !u2){
    // u1 与 u2 其中某一个为空,则将非空结点打上懒标记,优化了区间加的操作
    if(u1){
        maxv[u1] += max2;
        tag[u1] += max2;
    }
    if(u2){
        maxv[u2] += max1;
        tag[u2] += max1;
    }
    return u1 + u2;
}	

这里使用了懒标记优化了区间加的操作,因为在线段树合并的过程中,若两个结点的其中一个为空,另一个非空,说明空结点不需要被转移,则按照第二个状态转移式,我们需要往非空结点上加上空结点的转移贡献,而它是一个定值,因此这步操作等价于区间加,可以打懒标记优化;否则,若两个结点均空,则无需操作;若两个结点均非空,则继续向下合并,直到到达了两个叶节点且均非空,则两个结点都含有转移信息,还需要将其中一个结点的信息合并到另一个结点上,也就是:

if(nl == nr){
    maxv[u1] += max(maxv[u2], max2); // 不再是只加max2
}

\(max1, max2\) 分别表示两个线段树到当前结点表示区间的右侧的后缀,初始时当前结点表示整个区间,右侧并无区间,因此初值均设置为 0;在递归向下的过程中不断取右侧结点信息复合维护:

ls[u1] = merge(nl, mid, ls[u1], ls[u2], max(max1, maxv[rs[u1]]), max(max2, maxv[rs[u2]]));
rs[u1] = merge(mid + 1, nr, rs[u1], rs[u2], max1, max2);

每个点的 dp 值需要取选 \(u\) 和不选 \(u\) 两种情况的最大值,因此在 update 函数中的单点修改既不是单点加也不是单点赋值,而是需要与原值取最大值,表示两种情况的最优解:

int update(int u, int nl, int nr, int pos, int val){ // 单点修改 dp[u][pos] -> max(dp[u][pos], val)
	if(!u) u = ++tot;
	if(nl == nr){
		maxv[u] = max(maxv[u], val); // !
	}
	else{
		pushdown(u);
		int mid = nl + nr >> 1;
		if(pos <= mid) ls[u] = update(ls[u], nl, mid, pos, val);
		else rs[u] = update(rs[u], mid + 1, nr, pos, val);
		pushup(u);
	}
	return u;
}

最终答案即为:\(\max(dp_{1,1 \backsim m})\),更多细节见 code 部分。

code

P5298

备忘:

  • 每个结点最多只有两个子节点
  • 叶节点权值互不相同

先对所有权值做离散化,设值域为 \([1, m]\)

由于题目给出的概率值既不可能完全等于 0,也不可能完全等于 1,因此很明显对于根节点 1,所有叶节点的取值是都可以取得到的。因此 \(m\) 就等于叶节点的个数,我们只需要计算根节点 1 对于每个叶节点取得其权值的概率。

状态定义

\(dp_{u,i}\):结点 \(u\) 权值为 \(i\) 的概率

答案即在 \(dp_{1,1 \backsim m}\) 中。

状态转移

考虑子树 \(u\)

  1. \(u\) 无子节点,即 \(u\) 是叶节点:

\[dp_{u,w[u]}=1,其他为0 \]

  1. \(u\) 只有一个子节点,设为 \(v\)

\[ dp_{u,i} = dp_{v,i} (\forall i \in [1,m]) \]

  1. \(u\) 有两个子节点,设为 \(a,b\)
    • \(u\) 以概率 \(p_{u}\) 取子节点最大值时:

\[dp_{u,i} \leftarrow p_{u} * (dp_{a,i}*dp_{b,i+1 \backsim m} + dp_{b,i}*dp_{a,i+1 \backsim m}) \]

  • \(u\) 以概率 \(1 - p_{u}\) 取子节点最大值时:

\[dp_{u,i} \leftarrow (1 - p_{u}) * (dp_{a,i}*dp_{b,1 \backsim i-1} + dp_{b,i}*dp_{a,1 \backsim i-1}) \]

注意不需要考虑 \(a,b\) 权值相同的情况,因为 \(a,b\) 子树互不相交,包含的叶节点集合交集为空,而叶节点权值互不相同,因此 \(a,b\) 的取值一定不同。

改写一下情况 3 的状态转移式:
pZhWCOs.png

会发现其实这和上一道题是类似的,只不过需要多维护一个前缀信息,与维护后缀信息的方法完全相同。

实现细节

\(a, b\) 的线段树合并函数 merge 中,需要维护两个复合信息 \(sum1, sum2\)

\[sum1 = dp_{a,1 \backsim i-1}*p + dp_{a,i+1 \backsim m}*(1-p) \]

即上面的方括号式部分。

\[sum2 = dp_{b,1 \backsim i-1}*p + dp_{b,i+1 \backsim m}*(1-p) \]

即上面的方括号式部分。

维护方法见代码。

在线段树合并递归向下的过程中,考虑 \(a, b\) 的两个子节点空或非空的情况:

  1. \(a,b\) 均为空:说明在当前区间内不存在信息供更新 \(u\) 的信息,因此直接返回。
  2. \(a\) 非空,\(b\) 空:只需要将 \(a\) 在当前区间内的信息填到 \(u\) 上,还需要乘上一个 \(sum2\)。这相当于区间乘,可以用乘法懒标记来优化。
  3. \(a\) 空,\(b\) 非空:只需要将 \(b\) 在当前区间内的信息填到 \(u\) 上,还需要乘上一个 \(sum1\)。这相当于区间乘,可以用乘法懒标记来优化。
  4. \(a,b\) 均非空:若还未到叶节点就继续递归向下走;否则在叶节点处直接合并 \(a,b\) 两个叶节点的信息。

更多实现细节见下方 code 部分。

code

P6773

code

线段树分裂

pZhBcqO.png

感觉用处不是很大,以后碰到题再去学。

模板题:

P5494

posted @ 2026-01-21 23:49  小橘奏  阅读(20)  评论(0)    收藏  举报