树链剖分笔记
树链剖分:
可以把路径分割成
概念:
- 重/轻儿子:当前节点的子节点中子树最大的子结点称为该节点的 重儿子,其余都为 轻儿子
- 重/轻边:当前节点到 重儿子 的边称为 重边,到 轻儿子 的边称为 轻边
- 重链:由 重边 构成的 极大路径
->区间问题好解决,考虑序列化,链不就变成区间了。
->能不能把路径拆开,拆成一条条链的组合?
->考虑拆成重链组合.
->首先,将其序列化。
->优先遍历重边,这样重链一定是连续区间。
->按重链拆,树链上每走一条轻边,子树大小就
->复杂度保证了
->路径拆分方法:不断跳重链头,(两个点,跳较深的那一个,由于不确定会不会跳过去),跳到一条重链停止。就拆好了。
->在这个路径上,该干嘛干嘛,最后加起来(要求可加性).
不可加有欧拉序。
https://www.acwing.com/problem/content/description/2570/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010, M = N << 1;
int n, m;
int h[N], e[M], ne[M], w[N], idx;
int top[N], fa[N], son[N];
int depth[N], siz[N];
int dfn[N], timestamp, id[N];
struct Node
{
int l, r; LL sum;
}tr[N << 2];
LL add[N << 2];
void add_e(int a, int b)
{
e[ ++ idx] = b, ne[idx] = h[a], h[a] = idx;
}
Node pushup(Node a, Node b)
{
return {a.l, b.r, a.sum + b.sum};
}
inline int len(Node a) { return a.r - a.l + 1; }
Node BrushAndTag(int u, int k)
{
tr[u].sum += len(tr[u]) * k;
add[u] += k;
return tr[u];
}
void pushdown(int u)
{
BrushAndTag(u << 1, add[u]);
BrushAndTag(u << 1 | 1, add[u]);
add[u] = 0;
}
Node build(int u, int l, int r)
{
if (l == r) return tr[u] = {l, r, w[dfn[r]]};
int mid = (l + r) >> 1;
return tr[u] = pushup(build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r));
}
Node modify(int u, int l, int r, int v)
{
if (tr[u].r < l || tr[u].l > r) return tr[u];
if (tr[u].l >= l && tr[u].r <= r) return tr[u] = BrushAndTag(u, v);
pushdown(u); //注意!!!
return tr[u] = pushup(modify(u << 1, l, r, v), modify(u << 1 | 1, l, r, v));
}
Node query(int u, int l, int r)
{
if (tr[u].r < l || tr[u].l > r) return tr[0];
if (tr[u].l >= l && tr[u].r <= r) return tr[u];
pushdown(u);
return pushup(query(u << 1, l, r), query(u << 1 | 1, l, r));
}
void dfs1(int u, int father)
{
depth[u] = depth[father] + 1, siz[u] = 1, fa[u] = father;
for (int i = h[u]; i; i = ne[i])
{
int j = e[i];
if (j == father) continue;
dfs1(j, u);
siz[u] += siz[j];
if (siz[son[u]] < siz[j]) son[u] = j;
}
}
void dfs2(int u, int t)
{
top[u] = t; dfn[ ++ timestamp] = u, id[u] = timestamp;
if (!son[u]) return;
dfs2(son[u], t);
for (int i = h[u]; i; i = ne[i])
{
int j = e[i];
if (j == fa[u] || j == son[u]) continue;
dfs2(j, j);
}
}
void update_path(int x, int y, int v)
{
while (top[x] != top[y]) //拆边
{
if (depth[top[x]] < depth[top[y]]) swap(x, y); //注意这里是top,跳更低的地方。防止跳过lca/同一重链
modify(1, id[top[x]], id[x], v);
x = fa[top[x]];
}
if (depth[x] < depth[y]) swap(x, y);
modify(1, id[y], id[x], v);
}
LL query_path(int x, int y)
{
LL res = 0;
while (top[x] != top[y])
{
if (depth[top[x]] < depth[top[y]]) swap(x, y);
res += query(1, id[top[x]], id[x]).sum;
x = fa[top[x]];
}
if (depth[x] < depth[y]) swap(x, y);
res += query(1, id[y], id[x]).sum;
return res;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
for (int i = 0; i < n - 1; i ++ )
{
int a, b;
scanf("%d%d", &a, &b);
add_e(a, b), add_e(b, a);
}
dfs1(1, 0); //子树,深度,重儿子, 父亲
dfs2(1, 1); //重链
build(1, 1, n); //线段树
scanf("%d", &m);
while (m -- )
{
int opt, a, b, c;
scanf("%d%d", &opt, &a);
if (opt == 1)
{
scanf("%d%d", &b, &c);
update_path(a, b, c); //更新路径
}
else if (opt == 2)
{
scanf("%d", &b);
modify(1, id[a], id[a] + siz[a] - 1, b);
}
else if (opt == 3)
{
scanf("%d", &b);
cout << query_path(a, b) << endl; //问询路径
}
else cout << query(1, id[a], id[a] + siz[a] - 1).sum << endl;
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效