重链剖分介绍
重链剖分是将一棵树剖分成若干条重链的一种算法。在了解重链剖分前,我们需要知道一些基本的概念:
- 重儿子:一个结点的所有儿子中,size最大的那个(若有多 个任选一个)。
- 重链:由链顶和连续的重儿子所组成的链称为重链。
- 链顶:一条重链中深度最小的点。
例如在右图中,1的重儿子为2,5的重儿子为9,其中1,3,4,7,8,11,12都是链顶,1->2->5->9->10->13是一条重链,8自身构成一条重链。
重链剖分的性质
- 任意一条简单路径至多经过logn条重链,实际上少得多。因为每次切换一条重链,会使得子树大小减小一半,于是对于任意一条路径“重链切换次数”不会超过logn次。
- 每个节点都属于且仅属于一条重链。
- 重链会将整棵树完全部分。
- 在一条重链上,其结点的dfs序是自上而下连续的。
如何找到重链
top[x] 表示x节点的链顶节点编号,只要处理出了这个数组,就认为是完成剖分了。
通过两次dfs来找重链:
第一次dfs处理出重儿子、子树大小、节点深度、父节点等基础信息;
第二次dfs计算出所有结点的链顶和dfn,具体流程请看代码。
void dfs1(int x, int father) {
fa[x] = father; // 记录父节点
sz[x] = 1; // 子树大小
dep[x] = dep[fa[x]] + 1; // 节点深度
for(const auto &y : g[x]) {
if(y == fa[x]) continue;
dfs1(y, x);
sz[x] += sz[y];
if(sz[y] > sz[son[x]]) son[x] = y;
}
}
// 计算得到top
int tot = 0;
void dfs2(int x, int t) {
top[x] = t; // t表示链顶
dfn[x] = ++ tot;
idx[dfn[x]] = x;
if(son[x]) dfs2(son[x], t); // 重儿子的链顶继承
for(const auto &y : g[x]) {
if(y == fa[x] || y == son[x]) continue;
dfs2(y, y); // 轻边,重新开一个链顶
}
}
重链剖分的应用
求LCA
- 先跳到同一条重链上,即每次让top深度大的往上跳,跳到top的father位置。这个过程也是重链剖分的核心操作。
- 当两个点到同一条重链后,深度较小的就是lca。
int lca(int u, int v) {
while(top[u] != top[v]) {
if(dep[top[u]] > dep[top[v]]) u = fa[top[u]];
else v = fa[top[v]];
}
return dep[u] < dep[v] ? u : v;
}
维护路径信息(通常结合线段树)
- 每次将top深度较大的链维护,然后再往上跳,直到两个点在同一条重链上。
- 当两点在同一条重链上时直接维护信息即可。
ll u, v, w; cin >> u >> v >> w;
while(top[u] != top[v]) {
if(dep[top[u]] < dep[top[v]]) swap(u, v);
st.update(dfn[top[u]], dfn[u], w);
u = fa[top[u]];
}
if(dep[u] < dep[v]) swap(u, v);
st.update(dfn[v], dfn[u], w);
例子

#include <bits/stdc++.h>
using namespace std;
#define asd(i,a,b) for(int i=a;i<=b;i++)
#define int long long
const int N = 1e6 + 5;
int dfn[N]; int idx[N];
int n; vector<int>b[N];
int a[N]; int tot;
int fa[N]; int top[N]; int son[N]; int dep[N]; int sz[N];
struct tree {//线段树模板
int t[N]; int lz[N];
void pushup(int o) {
t[o] = t[o << 1] + t[o << 1 | 1];
}
void build(int s = 1, int e = n, int o = 1) {
if (s == e) {
t[o] = a[idx[s]];
return;
}
int mid = (s + e) >> 1;
build(s, mid, o << 1);
build(mid + 1, e, o << 1 | 1);
pushup(o);
}
void pushdown(int s, int e, int o) {
if (lz[o]) {
int mid = (s + e) >> 1;
t[o << 1] += lz[o] * (mid - s + 1);
t[o << 1 | 1] += lz[o] * (e - mid);
lz[o << 1] += lz[o];
lz[o << 1 | 1] += lz[o];
lz[o] = 0;
}
}
void updata(int l, int r, int val, int s = 1, int e = n, int o = 1) {
if (l <= s && e <= r) {
t[o] += (e - s + 1) * val;
lz[o] += val;
return;
}
int mid = (s + e) >> 1;
pushdown(s, e, o);
if (mid >= l) updata(l, r, val, s, mid, o << 1);
if (mid + 1 <= r) updata(l, r, val, mid + 1, e, o << 1 | 1);
pushup(o);
}
int query(int l, int r, int s = 1, int e = n, int o = 1) {
if (l <= s && e <= r) {
return t[o];
}
pushdown(s, e, o);
int res = 0;
int mid = (s + e) >> 1;
if (mid >= l) res += query(l, r, s, mid, o << 1);
if (mid + 1 <= r) res += query(l, r, mid + 1, e, o << 1 | 1);
return res;
}
}st;
void dfs1(int x, int pre)//重链模板
{
fa[x] = pre;
dep[x] = dep[pre] + 1;
sz[x] = 1;
for (int y : b[x])
{
if (y == pre)continue;
dfs1(y, x);
sz[x] += sz[y];
if (sz[y] > sz[son[x]])son[x] = y;
}
}
void dfs2(int x, int t)
{
top[x] = t;
dfn[x] = ++tot;
idx[dfn[x]] = x;
if (son[x])dfs2(son[x], t);
for (int y : b[x])
{
if (y == fa[x] || y == son[x])continue;
dfs2(y, y);
}
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cin.tie(0);
cin >> n;
asd(i, 1, n)cin >> a[i];
asd(i, 1, n - 1)
{
int u, v; cin >> u >> v;
b[u].push_back(v);
b[v].push_back(u);
}
dfs1(1, 0); dfs2(1, 1);
st.build();
int q; cin >> q;
while (q--)
{
int op; cin >> op;
if (op == 1) {
int u, v, w; cin >> u >> v >> w;
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
st.updata(dfn[top[u]], dfn[u], w);
u = fa[top[u]];
}
if (dep[u] < dep[v]) swap(u, v);
st.updata(dfn[v], dfn[u], w);
}
else if (op == 2) {
int u, w; cin >> u >> w;
st.updata(dfn[u], dfn[u] + sz[u] - 1, w);
}
else if (op == 3) {
int u, v; cin >> u >> v; int ans = 0;
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
ans += st.query(dfn[top[u]], dfn[u]);
u = fa[top[u]];
}
if (dep[u] < dep[v]) swap(u, v);
ans += st.query(dfn[v], dfn[u]);
cout<<ans<<endl;
}
else {
int u; cin >> u;
cout << st.query(dfn[u], dfn[u] + sz[u] - 1) << endl;
}
}
return 0;
}

#include <bits/stdc++.h>
using namespace std;
#define int long long // 使用long long类型
const int N = 1e6 + 5; // 定义最大节点数
// 树链剖分相关数组
int dfn[N]; // DFS序编号
int idx[N]; // 编号对应的原节点
int n, q; // 节点数和查询数
vector<pair<int, int>> b[N]; // 邻接表存储树结构,pair<节点,边权>
int a[N]; // 节点初始权值
int tot; // DFS序计数器
int c[N]; // 边权数组(按DFS序存储)
int fa[N]; // 父节点
int top[N]; // 链顶节点
int son[N]; // 重儿子
int dep[N]; // 节点深度
int sz[N]; // 子树大小
// 线段树结构体
struct tree {
struct node {
int num, sum; // num存储点权,sum存储边权
} t[N * 4];
int lznum[N * 4], lzsum[N * 4]; // 点权和边权的懒标记
// 上推更新节点信息
void pushup(int o) {
t[o].num = t[o << 1].num ^ t[o << 1 | 1].num; // 点权异或和
t[o].sum = t[o << 1].sum ^ t[o << 1 | 1].sum; // 边权异或和
}
// 构建线段树
void build(int s = 1, int e = n, int o = 1) {
lznum[o] = lzsum[o] = 0; // 初始化懒标记
if (s == e) {
t[o].num = a[idx[s]]; // 初始化点权
t[o].sum = c[s]; // 初始化边权
return;
}
int mid = (s + e) >> 1;
build(s, mid, o << 1); // 构建左子树
build(mid + 1, e, o << 1 | 1); // 构建右子树
pushup(o); // 更新当前节点
}
// 下推懒标记
void pushdown(int s, int e, int o) {
if (lznum[o] || lzsum[o]) {
int mid = (s + e) >> 1;
if (lznum[o]) { // 处理点权懒标记
t[o << 1].num ^= ((mid - s + 1) % 2) * lznum[o]; // 左子节点更新
t[o << 1 | 1].num ^= ((e - mid) % 2) * lznum[o]; // 右子节点更新
lznum[o << 1] ^= lznum[o]; // 下推懒标记
lznum[o << 1 | 1] ^= lznum[o];
lznum[o] = 0; // 清空当前懒标记
}
if (lzsum[o]) { // 处理边权懒标记
t[o << 1].sum ^= ((mid - s + 1) % 2) * lzsum[o];
t[o << 1 | 1].sum ^= ((e - mid) % 2) * lzsum[o];
lzsum[o << 1] ^= lzsum[o];
lzsum[o << 1 | 1] ^= lzsum[o];
lzsum[o] = 0;
}
}
}
// 区间更新
void update(int l, int r, int val, int s, int e, int o, int type) {
if (l > r) return; // 无效区间直接返回
if (l <= s && e <= r) { // 完全包含区间
if (type == 0) { // 更新点权
t[o].num ^= ((e - s + 1) % 2) * val; // 根据区间长度奇偶性决定是否异或
lznum[o] ^= val; // 设置懒标记
}
else { // 更新边权
t[o].sum ^= ((e - s + 1) % 2) * val;
lzsum[o] ^= val;
}
return;
}
pushdown(s, e, o); // 下推懒标记
int mid = (s + e) >> 1;
if (l <= mid) update(l, r, val, s, mid, o << 1, type); // 更新左子树
if (r > mid) update(l, r, val, mid + 1, e, o << 1 | 1, type); // 更新右子树
pushup(o); // 上推更新
}
// 区间查询
int query(int l, int r, int s, int e, int o, int type) {
if (l > r) return 0; // 无效区间返回0
if (l <= s && e <= r) { // 完全包含区间
return type == 0 ? t[o].num : t[o].sum; // 返回点权或边权
}
pushdown(s, e, o); // 下推懒标记
int mid = (s + e) >> 1;
int res = 0;
if (l <= mid) res ^= query(l, r, s, mid, o << 1, type); // 查询左子树
if (r > mid) res ^= query(l, r, mid + 1, e, o << 1 | 1, type); // 查询右子树
return res;
}
} st;
// 第一次DFS:计算父节点、深度、子树大小和重儿子
void dfs1(int x, int pre) {
fa[x] = pre; // 记录父节点
dep[x] = dep[pre] + 1; // 计算深度
sz[x] = 1; // 初始化子树大小
son[x] = 0; // 初始化重儿子
for (auto y : b[x]) { // 遍历所有子节点
if (y.first == pre) continue; // 跳过父节点
dfs1(y.first, x); // 递归处理子节点
sz[x] += sz[y.first]; // 累加子树大小
if (sz[y.first] > sz[son[x]]) son[x] = y.first; // 更新重儿子
}
}
// 第二次DFS:进行树链剖分
void dfs2(int x, int t) {
top[x] = t; // 记录链顶
dfn[x] = ++tot; // 分配DFS序编号
idx[dfn[x]] = x; // 记录编号对应的原节点
if (son[x]]) { // 如果有重儿子
dfs2(son[x], t); // 优先处理重儿子(保持链连续)
for (auto y : b[x]) { // 处理轻儿子
if (y.first == fa[x] || y.first == son[x]) continue;
dfs2(y.first, y.first); // 轻儿子作为新链的起点
}
}
// 存储边权(轻边和重边)
for (auto y : b[x]) {
if (y.first == fa[x]) continue; // 跳过父节点
c[dfn[y.first]] = y.second; // 将边权存储到子节点的DFS序位置
}
}
signed main() {
ios::sync_with_stdio(false); // 加速输入输出
cin.tie(0);
cout.tie(0);
// 输入树的基本信息
cin >> n >> q;
for (int i = 1; i <= n; ++i) cin >> a[i]; // 节点权值
for (int i = 1; i < n; ++i) { // 树的边
int u, v, c;
cin >> u >> v >> c;
b[u].push_back({ v, c });
b[v].push_back({ u, c });
}
// 树链剖分预处理
dfs1(1, 0); // 第一次DFS
dfs2(1, 1); // 第二次DFS
st.build(); // 构建线段树
// 处理查询
while (q--) {
int op, u, v, w;
cin >> op;
if (op == 1) { // 操作1:路径边权异或
cin >> u >> v >> w;
while (top[u] != top[v]) { // 当不在同一条链上
if (dep[top[u]] < dep[top[v]]) swap(u, v); // 选择深度较大的链
st.update(dfn[top[u]], dfn[u], w, 1, n, 1, 1); // 更新当前链的边权
u = fa[top[u]]; // 跳到链顶的父节点
}
if (u == v) continue; // 相同节点不需要处理
if (dep[u] < dep[v]) swap(u, v);
st.update(dfn[v] + 1, dfn[u], w, 1, n, 1, 1); // 更新最后一段的边权(不包括LCA)
}
else if (op == 2) { // 操作2:路径点权异或
cin >> u >> v >> w;
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
st.update(dfn[top[u]], dfn[u], w, 1, n, 1, 0); // 更新当前链的点权
u = fa[top[u]];
}
if (dep[u] < dep[v]) swap(u, v);
st.update(dfn[v], dfn[u], w, 1, n, 1, 0); // 更新最后一段的点权(包括LCA)
}
else if (op == 3) { // 操作3:查询路径点权异或和
cin >> u >> v;
int ans = 0;
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
ans ^= st.query(dfn[top[u]], dfn[u], 1, n, 1, 0); // 查询当前链的点权异或和
u = fa[top[u]];
}
if (dep[u] < dep[v]) swap(u, v);
ans ^= st.query(dfn[v], dfn[u], 1, n, 1, 0); // 查询最后一段的点权异或和
cout << ans << '\n';
}
else if (op == 4) { // 操作4:查询节点权值与相邻边权异或和
cin >> u;
int ans = st.query(dfn[u], dfn[u], 1, n, 1, 0); // 节点权值
for (auto y : b[u]) { // 遍历所有相邻边
if (y.first == fa[u]) { // 父节点方向的边
ans ^= st.query(dfn[u], dfn[u], 1, n, 1, 1); // 查询边权
}
else { // 子节点方向的边
ans ^= st.query(dfn[y.first], dfn[y.first], 1, n, 1, 1);
}
}
cout << ans << '\n';
}
}
return 0;
}
1463

被折叠的 条评论
为什么被折叠?



