• 关于萌新对树链剖分的理解


    可能更优的阅读体验

    仍在更新

    前言

    关于我突然补起树剖这件事呢,其实是因为最近做的题可以用树剖做,结果发现我不会树剖,就开始恶补了树剖。

    树剖是什么

    树剖,即树链剖分。

    树链剖分的定义:

    一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构(树状数组、BST、SPLAY、线段树等)来维护每一条链。——百度百科

    首先声明几个定义:

    1. 重儿子:一个点的儿子中子树大小最大的儿子。
    2. 轻儿子:一个点的儿子中除了重儿子的儿子。(注:叶子节点既没有重儿子也没有轻儿子,因为他没有儿子)
    3. 重边:连接父亲和重儿子的边。
    4. 轻边:连接父亲和轻儿子的边。
    5. 重链:相邻重边连起来的,连接重儿子的一条链。(但是起点是轻儿子)

    那么树链剖分可以处理什么问题呢?

    • 更改路径上的值
    • 查询一条路径的总和
    • 更改一个子树的值
    • 查询一个子树的值

    先用两个 ( ext{dfs})​ 来预处理一些东西。

    dfs1

    这个 ( ext{dfs})​ 求出每个的点的父亲、深度、子树大小和重儿子。

    void dfs1(int x,int fa)
    {
    	f[x]=fa;//父亲
        deep[x]=deep[fa]+1;//深度
        size[x]=1;//大小
    	for (int i=a[x].head;i;i=a[i].next)
    	{
    		int v=a[i].to;
    		if (v==fa) continue;
    		dfs1(v,x);//深搜儿子
    		size[x]+=size[v];//大小传递
    		if (size[v]>=size[son[x]]) son[x]=v;//求出重儿子
    	}
    }
    

    dfs2

    这个 ( ext{dfs}) 记录每个点的新编号、新编号的值和所处重链的开头。

    void dfs2(int x,int tp)
    { 
    	top[x]=tp;//重链开头
    	id[x]=++cnt;//新编号
        v1[cnt]=val[x];//重新赋值
    	if (!son[x]) return;//叶子结点没有重儿子,结束
    	dfs2(son[x],tp);//先重儿子后轻儿子,理由见下
    	for (int i=a[x].head;i;i=a[i].next)
    	{
    		int v=a[i].to;
    		if (v!=f[x]&&v!=son[x]) dfs2(v,v);//轻儿子的重链开头是他本身
    	}
    }
    

    处理问题

    那么在 ( ext{dfs2}) 中为什么要先走重儿子后走轻儿子呢?

    模拟一下:

    容易看出图中的重儿子有 2、4、6、5、8,有四条重链(用红框框住的),同时由于先走重儿子的缘故,重链上的编号是连续的。

  • 相关阅读:
    【bzoj1901】dynamic ranking(带修改主席树)
    一堆乱七八糟绝不正经的排序算法
    bzoj2588 Spoj10628. count on a tree
    poj2104 K-th Number
    【bzoj 3595】: [Scoi2014]方伯伯的Oj
    bzoj1926: [Sdoi2010]粟粟的书架
    P3168 [CQOI2015]任务查询系统
    进程和线程
    History of AI
    es6
  • 原文地址:https://www.cnblogs.com/Livingston/p/15122312.html
Copyright © 2020-2023  润新知