(笔记)树链剖分 重链剖分 长链剖分 虚实链剖分 部分 0/1分数规划

树链剖分

树链剖分是一类常用的树上问题处理方式。

重链剖分

树上有若干个关于路径与子树的修改与询问,重链剖分+线段树可以在 \(O(n\log^2 n)\) 的时间内解决这样的问题。

具体来说,每个点都有一个重儿子即为 \(son_u\),这个点在 \(u\) 的所有儿子中子树大小最大。所有 \(u\)\(son_u\) 连边叫作重边,其余都是轻边。重边组成的链叫作重链。关于轻边,从下至上经过一条轻边子树大小至少翻倍,所以如果每条重链只访问一次,最多只会跳 \(O(\log n)\) 次。

我们在每个重链上搞一颗线段树(或者当有子树查询时,开一颗全局线段树),每次对应修改查询就可以达到我们的目的。

P3979 遥远的国度(换根重链剖分)

考虑三种情况:

查询节点 \(u\),当前钦定根节点 \(root\) 和原本根节点 \(1\)

  1. \(u=root\),直接返回 \(ans[1]\)
  2. \(u\notin 1\rightarrow root\),直接返回 \(ans[u]\)
  3. \(u\in 1\rightarrow root\),找到 \(u\rightarrow root\) 这条路径上的 \(u\) 的第一个相邻节点 \(v\),将线段树全局信息抠掉子树 \(v\) 即为所求。

注意此类问题通常需要用一些特别的方法达到换根目的,其核心思想是定根转换根,讨论每次换根 \(1\rightarrow root\) 这条路径上变化对答案的影响然后做即可。

CF916E Jamie and Tree

本题有一个很诡异的分讨。

和上一题一样,这主要是换根操作。但是有一个换根 LCA 的东西看起来根本不是人。具体怎么做呢?画个图,然后分类讨论当前 \(root\) 是否在 \(x\rightarrow y\) 上,在的话这个 LCA 就应该是 \(root\)。除此之外,还应该分别考虑如果不在,是在 \(LCA(x,y)\)(以下 \(LCA(x,y)\) 建立在 \(1\) 为树根基础上)的上方还是在路径的下面。其实这个东西的本质就是如果在下面,找到 \(root\) 和路径 \(x\rightarrow y\) 的邻接点的上一个点 \(u,\exist v\in x\rightarrow y,v\rightarrow u\rightarrow root\),且 \(v,u\) 直接相连,然后把这棵子树抠掉。如果在上面,或者 \(LCA(LCA(x,y),root)\) 根本不在以 \(LCA(x,y)\) 为根的子树里(\(1\) 为全局根),那么直接对 \(LCA(x,y)\) 子树尽心该操作即可。

虚实链剖分

经典应用:LCT。

具体来说,在一颗 BST 中,一个节点具有虚实儿子。虚儿子无法直接被父节点访问,而实儿子可以。

在维护 LCT access 操作时,虚实链剖分起到了重要的作用。从下至上的操作中,我们得以修改 Splay 森林的结构,同时保留了从下至上访问的特性。

长链剖分

最常用的是长链剖分优化 DP,这类 DP 方程中都含有深度这一下标。

该剖分的方法与重链剖分大同小异,唯一不同的是 \(son_u\) 表示的是 \(u\) 的儿子中,子树深度最大的节点,其中子树深度表示一颗子树中存在的任意节点 \(v\) 到树根 \(root\) 的距离的最大值。

我们在 DP 时,采用重儿子直接继承,轻儿子暴力修改的方式优化。为什么这样是对的?每条重链都需要重新开一个 DP 数组,每次合并时,轻儿子数组长度就是所在重链的长度。由于所有重链长度和为 \(n\),总时间复杂度就是 \(O(n)\) 的。

P5904 [POI 2014] HOT-Hotels 加强版

下面我们来具体解读一下例题P5904 [POI 2014] HOT-Hotels 加强版

这里我们设计的状态是 \(f_{u,i}\) 表示以 \(u\) 为根的子树中距离 \(u\)\(i\) 的节点数,\(g_{u,i}\) 表示以 \(u\) 为根的子树中存在的两个任意节点 \(x,y(x\neq y)\) 的无序计数,且满足 \(dis(x,lca(x,y))=lca(y,lca(x,y))=dis(u,lca(x,y))+i\)

我们的计数有两种情况:

\((i,j,k)\) 中,\(j,k\) 深度相同,分类讨论。

  1. \(i\)\(j,k\) 的若干级祖先。

这里我们的转移很简单,即为 \(ans\leftarrow g_{i,0}\)

  1. \(i\) 不是 \(j,k\) 的祖先。

假设 \(u=lca(i,j)=lca(i,k)\),在转移过程中,我们要拥有先前访问的所有子节点的信息记录在 \(u\) 的 DP 数组中,然后类似前缀和地计算答案。因此请注意,下面的转移方程中 \(f_u,g_u\) 都是前缀状态,\(len\) 表示的是期望的三元组到中心点的距离。

\[\begin{aligned} v&\in son(u) \\ ans&\leftarrow f_{u,len}\times g_{v,len+1} \\ ans&\leftarrow f_{v,len-1}\times g_{u,len} \end{aligned} \]

我们设想我们在节点 \(u\) 匹配 \(i,j,k\),那么 \(g\) 数组的设计其实就相当于扣掉了一部分距离,找到另一颗子树补回去即可。

除此之外还有自转移:

\[\begin{aligned} g_{u,i}&\leftarrow f_{u,i}\times g_{v,i-1} \\ g_{u,i}&\leftarrow g_{v,i+1} \\ f_{u,i}&\leftarrow f_{v,i-1} \end{aligned} \]

这些在代码设计中都有体现。注意这些转移的顺序不能调换。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+5;
int n,dep[N],son[N];
LL ans,*f[N],*g[N],p[N<<2],*o=p;
vector<int>G[N];
void dfs(int u,int fa){
	for(int v:G[u]){
		if(v==fa)continue;
		dfs(v,u);
		if(dep[son[u]]<dep[v])
			son[u]=v;
	}
	dep[u]=dep[son[u]]+1;
}
void dfs1(int u,int fa){
	if(son[u])
		f[son[u]]=f[u]+1,g[son[u]]=g[u]-1,dfs1(son[u],u);
	f[u][0]=1,ans+=g[u][0];
	for(int v:G[u]){
		if(v==fa||v==son[u])continue;
		f[v]=o,o+=dep[v]<<1;
		g[v]=o,o+=dep[v]<<1;
		dfs1(v,u);
		for(int i=0;i<dep[v];i++){
			if(i)ans+=f[u][i-1]*g[v][i];
			ans+=g[u][i+1]*f[v][i];
		}
		for(int i=0;i<dep[v];i++){
			g[u][i+1]+=f[u][i+1]*f[v][i];
			if(i)g[u][i-1]+=g[v][i];
			f[u][i+1]+=f[v][i];
		}
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs(1,0);
	f[1]=o;o+=dep[1]<<1;
	g[1]=o;o+=dep[1]<<1;
	dfs1(1,0);
	cout<<ans;
	return 0;
}

P6074 最小路径

这题很板,只需要掌握分数规划的基本技巧即可。

\[\begin{aligned} \frac{\sum a_i}{\sum b_i}&\geq mid \\ \sum a_i &\geq \sum b_i\times mid \\ \sum (a_i - b_i\times mid)&\geq 0 \end{aligned} \]

每个点权为 \(a_i-b_i\times mid\),求最小点权和路径即可。这里需要注意的是,在实现过程中,DP 方程是直接由重儿子原位覆盖到父亲上的,所以所有 DP 值的设计都要是绝对的值而不是相对的值。

P4292 [WC2010] 重建计划

和上一题一样,也是 0/1 分数规划问题的经典应用。在这里可以用长链剖分来维护,但是有个问题就是答案需要用到长链剖分数组上区间查询。这好办,直接动态开点线段树,搞一颗独立维护信息的东西。这样长链剖分数组的作用就仅仅是帮助程序在 \(O(n)\) 的时间内扫出轻儿子的数组信息,然后区间查询应用线段树即可,总时间复杂度 \(O(n\log^2 n)\)。当然,还有更优解,可以用单调队列合并与帮助维护长链剖分信息,但是人太懒了,遂弃之。

posted @ 2025-04-22 11:53  TBSF_0207  阅读(39)  评论(0)    收藏  举报