树链剖分笔记

树链剖分:

可以把路径分割成logn个区间。
概念:

  1. 重/轻儿子:当前节点的子节点中子树最大的子结点称为该节点的 重儿子,其余都为 轻儿子
  2. 重/轻边:当前节点到 重儿子 的边称为 重边,到 轻儿子 的边称为 轻边
  3. 重链:由 重边 构成的 极大路径

->区间问题好解决,考虑序列化,链不就变成区间了。
->能不能把路径拆开,拆成一条条链的组合?
->考虑拆成重链组合.
->首先,将其序列化。
->优先遍历重边,这样重链一定是连续区间。
->按重链拆,树链上每走一条轻边,子树大小就/=2,所以最多走 logn 条轻边,复杂度O(logn)
->复杂度保证了
->路径拆分方法:不断跳重链头,(两个点,跳较深的那一个,由于不确定会不会跳过去),跳到一条重链停止。就拆好了。

->在这个路径上,该干嘛干嘛,最后加起来(要求可加性).

不可加有欧拉序。

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;
}
posted @   hhhhhua  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示