树链剖分

ps:好久没写博客了QAQ,今天补一次莫名其妙地模板吧。。

树链剖分

  • NOIP联赛经常涉及,虽然没有指名道姓的说用这种算法,可是用了树上差分就很懵逼了...这还不如用树链剖分呢...因为考试要经常涉及,所以我就简单学习了下。(再次鸣谢各路帮助我学习的大佬ymy)

  • 简单理解下,就是可以对树上一段连续区间进行操作,通过轻重链去剖分,要比直接用dfs序的要好,时间效率也比较高的(虽然不太明白怎么证明的),然后剖成一段段区间,对树上路径进行操作时,直接对区间进行操作。

  • 要想弄这个,首先必须学好线段树和树状数组和各种区间维护利器,这个才能将树链剖分的能量发挥出来。

预处理

  1. 首先DfsInit这个先处理出depfasizeson四个数组。

fa 记的是这个节点的父亲节点。

dep 为这个点的深度 更新为dep[u]=dep[fa[u]]+1

size 为这个节点的子树的大小(需要包括自己),更新为 size[u]=1+vsize[v],其中vu的儿子们。

son 就是这个节点的重儿子,所谓重就是这个点的size最大,表达为size[son[u]]=max(size[v])v的意义同上。

这个更新过程见程序:

int dep[N], sz[N], son[N], fa[N]; void Dfs_Init(int u, int from) { sz[u] = 1; dep[u] = dep[from] + 1; fa[u] = from; for (int i = Head[u]; i; i = Next[i]) { int v = to[i]; if (v == from) continue ; Dfs_Init(v, u); sz[u] += sz[v]; if (sz[v] > sz[son[u]] ) son[u] = v; } }

进行剖分

  1. 再用一个DfsPart再处理出topdfnnum三个数组,进行树链剖分。

dfn就是记这个点的dfs序,作为这个点在链上的位置,记录方式为dfn[u]=++clk

num就是记录dfn对应的点,即num[dfn[u]]=u,可以求出在链上的点在原来树上的位置

top就是记这个点所在重链的开始位置,转移为top[u]=son[fa[u]]==u?top[fa[u]]:u,意思为如果这个点是它父亲的重儿子,就继承它父亲的top,否则将自己当成重链的顶端。

程序为:

int top[N], dfn[N], num[N]; void Dfs_Part(int u) { static int clk = 0; dfn[u] = ++clk; num[clk] = u; top[u] = son[fa[u]] == u ? top[fa[u]] : u; if (son[u]) Dfs_Part(son[u]); for (int i = Head[u]; i; i = Next[i]) { int v = to[i] ; if (v == fa[u] || v == son[u]) continue ; Dfs_Part(v); } }

树上操作

  1. 剖完后我们就可以进行树上操作了,主要有两个:

1.对于一个点的子树进行更新和询问;

2.对于任意两个点上的路径进行更新和询问。

第一个操作比较简单,对于u的子树,我们只要对[dfn[u],dfn[u]+size[u]1]这一段连续区间进行更新就行了,因为其dfs序是连续的,可以直接修改。

第二个操作有些复杂,需要跳链操作,简单来说就是两个点不断向上跳,直到它们在同一条链上,在此期间,我们不断向上跳时要更新你需要更新的区间。

跳的过程就是,求Lca,对于两个点的xy,求出top[x]=utop[y]=v,比较uv的深度,如果dep[u]<dep[v]就交换一下uvxy,因为我们是要将深度更大的点跳上去,直到他们到同一条链上(即top[x]==top[y]),中间我们要更新的区间为[dfn[u],dfn[x]]。最后一下我们还要比较一下xy的深度,将dep小的那个放在前面,即如果dep[x]>dep[y]swap xy再更新[dfn[x],dfn[y]]

代码如下:

void Lca1(int x, int y, int z) { z %= Mod; while (top[x] != top[y]) { int u = top[x], v = top[y]; if (dep[u] < dep[v]) { swap(u, v); swap(x, y); } ul = dfn[u]; ur = dfn[x]; uv = z; Update(1, 1, n); x = fa[u]; } if (dep[x] > dep[y]) swap(x, y); ul = dfn[x]; ur = dfn[y]; uv = z; Update(1, 1, n); }

这个有道很好的裸题去做 Luogu【P3384】【模板】树链剖分 这个就是将两个修改、两个询问结合在一起,比较好做。

简单地附一个代码算了= = 应该还是比较容易看的……

#include <bits/stdc++.h> #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Set(a, v) memset(a, v, sizeof(a)) using namespace std; inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;} inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;} inline int read() { int x = 0, fh = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * fh; } void File() { #ifdef zjp_shadow freopen ("P3384.in", "r", stdin); freopen ("P3384.out", "w", stdout); #endif } int n, m, rt, Mod; const int N = 1e6 + 1e3; vector<int> G[N]; int dep[N], sz[N], son[N], fa[N]; void Dfs_Init(int u, int from) { sz[u] = 1; dep[u] = dep[fa[u] = from] + 1; for (int v : G[u]) if (v ^ fa[u]) { Dfs_Init(v, u), sz[u] += sz[v]; if (sz[v] > sz[son[u]]) son[u] = v; } } int dfn[N], num[N], clk, top[N]; void Dfs_Part(int u) { num[dfn[u] = ++ clk] = u; top[u] = (son[fa[u]] == u) ? top[fa[u]] : u; if (son[u]) Dfs_Part(son[u]); for (int v : G[u]) if ((v ^ fa[u]) && (v ^ son[u])) Dfs_Part(v); } int val[N]; #define lson o << 1, l, mid #define rson o << 1 | 1, mid + 1, r #define Add(o, l, r, v) (sumv[(o)] += 1ll * ((r) - (l) + 1) * (v) % Mod) %= Mod, (tag[(o)] += (v)) %= Mod struct Segment_Tree { int sumv[N << 1], tag[N << 1]; inline void Push_Down(int o, int l, int r) { if (!tag[o]) return ; int ls = o << 1, rs = ls | 1, mid = (l + r) >> 1; Add(ls, l, mid, tag[o]); Add(rs, mid + 1, r, tag[o]); tag[o] = 0; } inline void Push_Up(int o) { sumv[o] = sumv[o << 1] + sumv[o << 1 | 1]; } void Update(int o, int l, int r, int ul, int ur, int uv) { if (ul <= l && r <= ur) { Add(o, l, r, uv); return ; } int mid = (l + r) >> 1; Push_Down(o, l, r); if (ul <= mid) Update(lson, ul, ur, uv); if (ur > mid) Update(rson, ul, ur, uv); Push_Up(o); } int Query(int o, int l, int r, int ql, int qr) { if (ql <= l && r <= qr) { return sumv[o]; } int mid = (l + r) >> 1, res = 0; Push_Down(o, l, r); if (ql <= mid) res += Query(lson, ql, qr); if (qr > mid) res += Query(rson, ql, qr); Push_Up(o); return res % Mod; } void Build(int o, int l, int r) { if (l == r) { sumv[o] = val[num[l]]; return ; } int mid = (l + r) >> 1; Build(lson); Build(rson); Push_Up(o); } } T; void Update(int x, int y, int z) { while (top[x] ^ top[y]) { int u = top[x], v = top[y]; if (dep[u] < dep[v]) swap(u, v), swap(x, y); T.Update(1, 1, n, dfn[u], dfn[x], z); x = fa[u]; } if (dep[x] > dep[y]) swap(x, y); T.Update(1, 1, n, dfn[x], dfn[y], z); } int Query(int x, int y) { int res = 0; while (top[x] ^ top[y]) { int u = top[x], v = top[y]; if (dep[u] < dep[v]) swap(u, v), swap(x, y); (res += T.Query(1, 1, n, dfn[u], dfn[x])) %= Mod; x = fa[u]; } if (dep[x] > dep[y]) swap(x, y); (res += T.Query(1, 1, n, dfn[x], dfn[y])) %= Mod; return res; } int main () { File(); n = read(); m = read(); rt = read(); Mod = read(); For (i, 1, n) val[i] = read(); For (i, 1, n - 1) { int u = read(), v = read(); G[u].push_back(v); G[v].push_back(u); } Dfs_Init(rt, 0); Dfs_Part(rt); T.Build(1, 1, n); For (i, 1, m) { int opt = read(); if (opt == 1) { int x = read(), y = read(), z = read(); Update(x, y, z); } else if (opt == 2) { int x = read(), y = read(); printf ("%d\n", Query(x, y)); } else if (opt == 3) { int x = read(), z = read(); T.Update(1, 1, n, dfn[x], dfn[x] + sz[x] - 1, z); } else if (opt == 4) { int x = read(); printf ("%d\n", T.Query(1, 1, n, dfn[x], dfn[x] + sz[x] - 1)); } } return 0; }
  • 一些题目:
    POJ3237
    HDU2801
    SDOI2017

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/7750046.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(397)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示