ps:好久没写博客了QAQ,今天补一次莫名其妙地模板吧。。
树链剖分
NOIP联赛经常涉及,虽然没有指名道姓的说用这种算法,可是用了树上差分就很懵逼了...这还不如用树链剖分呢...因为考试要经常涉及,所以我就简单学习了下。(再次鸣谢各路帮助我学习的大佬ymy)
简单理解下,就是可以对树上一段连续区间进行操作,通过轻重链去剖分,要比直接用d f s d f s 序的要好,时间效率也比较高的(虽然不太明白怎么证明的),然后剖成一段段区间,对树上路径进行操作时,直接对区间进行操作。
要想弄这个,首先必须学好线段树和树状数组和各种区间维护利器,这个才能将树链剖分的能量发挥出来。
预处理
首先D f s I n i t D f s I n i t 这个先处理出d e p d e p 、f a f a 、s i z e s i z e 、s o n s o n 四个数组。
f a f a 记的是这个节点的父亲节点。
d e p d e p 为这个点的深度 更新为d e p [ u ] = d e p [ f a [ u ] ] + 1 d e p [ u ] = d e p [ f a [ u ] ] + 1 。
s i z e s i z e 为这个节点的子树的大小(需要包括自己),更新为 s i z e [ u ] = 1 + ∑ v s i z e [ v ] s i z e [ u ] = 1 + ∑ v s i z e [ v ] ,其中v v 为u u 的儿子们。
s o n s o n 就是这个节点的重儿子,所谓重就是这个点的s i z e s i z e 最大,表达为s i z e [ s o n [ u ] ] = max ( s i z e [ v ] ) s i z e [ s o n [ u ] ] = max ( s i z e [ v ] ) ,v v 的意义同上。
这个更新过程见程序:
进行剖分
再用一个D f s P a r t D f s P a r t 再处理出t o p t o p 、d f n d f n 、n u m n u m 三个数组,进行树链剖分。
d f n d f n 就是记这个点的d f s d f s 序,作为这个点在链上的位置,记录方式为d f n [ u ] = + + c l k d f n [ u ] = + + c l k 。
n u m n u m 就是记录d f n d f n 对应的点,即n u m [ d f n [ u ] ] = u n u m [ d f n [ u ] ] = u ,可以求出在链上的点在原来树上的位置
t o p t o p 就是记这个点所在重链的开始位置,转移为t o p [ u ] = s o n [ f a [ u ] ] == u ? t o p [ f a [ u ] ] : u t o p [ u ] = s o n [ f a [ u ] ] == u ? t o p [ f a [ u ] ] : u ,意思为如果这个点是它父亲的重儿子,就继承它父亲的t o p t o p ,否则将自己当成重链的顶端。
程序为:
树上操作
剖完后我们就可以进行树上操作了,主要有两个:
1.对于一个点的子树进行更新和询问;
2.对于任意两个点上的路径进行更新和询问。
第一个操作比较简单,对于u u 的子树,我们只要对[ d f n [ u ] , d f n [ u ] + s i z e [ u ] − 1 ] [ d f n [ u ] , d f n [ u ] + s i z e [ u ] − 1 ] 这一段连续区间进行更新就行了,因为其d f s d f s 序是连续的,可以直接修改。
第二个操作有些复杂,需要跳链操作,简单来说就是两个点不断向上跳,直到它们在同一条链上,在此期间,我们不断向上跳时要更新你需要更新的区间。
跳的过程就是,求L c a L c a ,对于两个点的x x 和y y ,求出t o p [ x ] = u t o p [ x ] = u 和t o p [ y ] = v t o p [ y ] = v ,比较u u 和v v 的深度,如果d e p [ u ] < d e p [ v ] d e p [ u ] < d e p [ v ] 就交换一下u u 和v v 、x x 和y y ,因为我们是要将深度更大的点跳上去,直到他们到同一条链上(即t o p [ x ] == t o p [ y ] t o p [ x ] == t o p [ y ] ),中间我们要更新的区间为[ d f n [ u ] , d f n [ x ] ] [ d f n [ u ] , d f n [ x ] ] 。最后一下我们还要比较一下x x 和y y 的深度,将d e p d e p 小的那个放在前面,即如果d e p [ x ] > d e p [ y ] d e p [ x ] > d e p [ y ] 就s w a p s w a p x x 和y y 再更新[ d f n [ x ] , d f n [ y ] ] [ d f n [ x ] , d f n [ y ] ] 。
代码如下:
这个有道很好的裸题去做 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__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 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】