树链剖分
ps:好久没写博客了QAQ,今天补一次莫名其妙地模板吧。。
树链剖分
-
NOIP联赛经常涉及,虽然没有指名道姓的说用这种算法,可是用了树上差分就很懵逼了...这还不如用树链剖分呢...因为考试要经常涉及,所以我就简单学习了下。(再次鸣谢各路帮助我学习的大佬ymy)
-
简单理解下,就是可以对树上一段连续区间进行操作,通过轻重链去剖分,要比直接用\(dfs\)序的要好,时间效率也比较高的(虽然不太明白怎么证明的),然后剖成一段段区间,对树上路径进行操作时,直接对区间进行操作。
-
要想弄这个,首先必须学好线段树和树状数组和各种区间维护利器,这个才能将树链剖分的能量发挥出来。
预处理
- 首先\(DfsInit\)这个先处理出\(dep\)、\(fa\)、\(size\)、\(son\)四个数组。
\(fa\) 记的是这个节点的父亲节点。
\(dep\) 为这个点的深度 更新为\(dep[u]=dep[fa[u]]+1\)。
\(size\) 为这个节点的子树的大小(需要包括自己),更新为 \(size[u]=1 + \sum _{v} size[v]\),其中\(v\)为\(u\)的儿子们。
\(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;
}
}
进行剖分
- 再用一个\(DfsPart\)再处理出\(top\)、\(dfn\)、\(num\)三个数组,进行树链剖分。
\(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.对于一个点的子树进行更新和询问;
2.对于任意两个点上的路径进行更新和询问。
第一个操作比较简单,对于\(u\)的子树,我们只要对\([dfn[u],dfn[u]+size[u]-1]\)这一段连续区间进行更新就行了,因为其\(dfs\)序是连续的,可以直接修改。
第二个操作有些复杂,需要跳链操作,简单来说就是两个点不断向上跳,直到它们在同一条链上,在此期间,我们不断向上跳时要更新你需要更新的区间。
跳的过程就是,求\(Lca\),对于两个点的\(x\)和\(y\),求出\(top[x]=u\)和\(top[y]=v\),比较\(u\)和\(v\)的深度,如果\(dep[u]<dep[v]\)就交换一下\(u\)和\(v\)、\(x\)和\(y\),因为我们是要将深度更大的点跳上去,直到他们到同一条链上(即\(top[x]==top[y]\)),中间我们要更新的区间为\([dfn[u],dfn[x]]\)。最后一下我们还要比较一下\(x\)和\(y\)的深度,将\(dep\)小的那个放在前面,即如果\(dep[x]>dep[y]\)就\(swap\) \(x\)和\(y\)再更新\([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