首页 > 其他分享 >树链剖分笔记

树链剖分笔记

时间:2024-06-22 17:58:58浏览次数:12  
标签:重链 剖分 路径 tr 笔记 树链 include 节点

树链剖分:

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

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

->区间问题好解决,考虑序列化,链不就变成区间了。
->能不能把路径拆开,拆成一条条链的组合?
->考虑拆成重链组合.
->首先,将其序列化。
->优先遍历重边,这样重链一定是连续区间。
->按重链拆,树链上每走一条轻边,子树大小就$ /= 2$,所以最多走 $log n $ 条轻边,复杂度\(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;
}

标签:重链,剖分,路径,tr,笔记,树链,include,节点
From: https://www.cnblogs.com/qinyiting/p/18262578

相关文章

  • Michael M. Tiller《Modelica多领域物理系统建模入门与提高》Chapter 4学习笔记
    文章目录第四章组件重用4.1概述4.2公共代码开发4.2.1识别和定义公共代码4.2.2使用公共代码定义模型4.3构建可重用的块4.3.1建立控制器模型4.3.2传递信息4.3.3小结4.4允许替换的组件4.4.1通用控制器接口4.4.2特定控制器模型4.4.3使用可替换组件4.4.4小结......
  • FFT & NTT 复习笔记
    快速变换设原多项式为\(F(x)=\sum_{i\in[0,n)}a_ix^i\),其中\(n=2^k,k\in\mathbbZ^+\)。我们要求\(\foralli\in[0,n),\hata_i=F(t_i)\),其中\(t\)是一个长度为\(n\)且两两互不相同的序列。显然\(F\)可以被一组\(\hata,t\)唯一确定,即点值表示......
  • 【笔记】表格处理(一)Apache POI
    表格处理ApachePOI表格处理一、简介HSSF和XSSF有啥不同?二、使用步骤(一)依赖(二)基础使用示例1.创建一个简单的Excel文件2.读取一个Excel文件3.设置单元格样式4.合并单元格5.添加图片6.数据有效性和下拉列表7.自动调整列宽8.公式计算9.日期和时间格式10.......
  • 【B站黑马程序员LINUX 学习笔记 01】
    课程看的是b站黑马程序员的https://www.bilibili.com/video/BV1n84y1i7td/?spm_id_from=333.337.search-card.all.click&vd_source=be621a30ea2e4e0374f5df95b0b017f201操作系统概述计算机由:硬件和软件组成。操作系统是计算机软件的一种,它主要负责:作为用户和计算机硬件......
  • 网络知识笔记
                                                                                         ......
  • 网络安全笔记
    1,网络设备链路设备:网络设备(对外提供网络服务) 终端设备(使用网络服务)路由器交换机防火墙   pc机,服务器osi参考模型开放系统互联的参考模型iso:国际标准化组织1.物理层:传输bit2.数据链层3.网络层4.传输层5.会话层6.表示层7.应用层1-3用户运用4-7数据传......
  • C++学习笔记----重载运算符
    运算符重载运算符重载可以在通过成员函数或者全局函数进行重载,编译器提供了内置的运算符;我们可以通过定义对应的函数和参数进行重载,也可以使用编译器提供的名称`operator运算符()`进行重载;运算符重载本质是对内置的运算符函数进行重载:函数相同,参数不同;返回引用和地址需要思......
  • JAVA学习笔记DAY10——SpringBoot基础
    文章目录SpringBoot3介绍SpringBoot快速入门@SpringBootApplicationSpringBoot配置文件统一配置管理Yaml配置优势tipsSpringBoot整合SpringMVC静态资源拦截器interceptorSpringBoot整合DruidSpringBoot整合MybatisSpringBoot整合txaopSpringBoot打包......
  • 2024.06.22【读书笔记】丨生物信息学与功能基因组学(第十七章 人类基因组 第一部分)【AI
    第一部分:人类基因组概述与测序历史(详细版)摘要:第十七章深入探讨了人类基因组的复杂性、测序历程以及其对现代科学的意义。人类基因组由约30,000至40,000个蛋白质编码基因组成,这些基因的表达和变异构成了我们生物学特征和疾病倾向的基础。本章节详细回顾了人类基因组计划的......
  • Manacher学习笔记
    1.用途给定一个串,求该串中最长回文子串的长度2.算法过程2.1.预处理回文串分为两类,奇回文和偶回文,所以对称中点是不确定的,可能是字符也可能是两个字符之间的空隙所以可以在任意两个字符和开头结尾加上同一个奇怪的字符,此时的回文中心就一定是一个字符2.2.算法主体定义数......