首页 > 其他分享 >学习笔记:重链剖分

学习笔记:重链剖分

时间:2023-03-07 22:59:41浏览次数:73  
标签:son 剖分 笔记 id fa 重链 节点 size

重链剖分

前置芝士:线段树,dfs 序,LCA

概念

  • 一个非叶子节点,他的儿子中子树大小最大的就是重儿子,否则就是轻儿子
  • 对于一条边,如果连接的子节点是重儿子,那么这条边就是重边,否则就是轻边
  • 重链:如果一条链中的边都是重边,那么这条链就是重链

重要结论

一个节点到根节点的路径上最多不超过 \(\log n\) 条重链

证明

(以下用 \(now\) 表示当前节点,\(fa\) 表示 \(now\) 的父节点,\(son_{fa}\) 表示父节点的重儿子,\(size_u\) 表示 \(u\) 子树的大小)

由于经过 \(\log n\) 条重链后,那么就会经过 \(\log n\) 条轻边

那么只需要证明经过一条轻边后 \(size\) 至少会变为原来的 \(2\) 倍就行了

再分类讨论一下:

如果 \(size_{son_{fa}} \le \dfrac{size_{fa}}2\) ,那么显然可以得到 \(size_{now} \le \dfrac{size_{fa}}2\) ,那么经过这条轻边后 \(size\) 至少会变成原来的 \(2\) 倍

否则,$size_{son_{fa}} > \dfrac{size_{fa}}2 $,那么由于 \(size_{now}\le size_{fa}-size_{son_{fa}}\),那么可以得到 \(size_{now} < \dfrac {size_{fa}} 2\),那么经过这条轻边后 \(size\) 至少也会变成原来的 \(2\) 倍

dfs1

dfs 时我们需要处理出以下数组:

  • \(fa_u\) :表示 \(u\) 节点的父节点

  • \(depth_u\) :表示 \(u\) 节点的深度

  • \(size_u\) :表示以 \(u\) 节点为根的子树的大小

  • \(son_u\) :表示 \(u\) 节点的重儿子

Code

(代码里的注释已经讲的很清楚了)

void dfs1(int u,int f)
{
	fa[u]=f,size[u]=1;
	depth[u]=depth[f]+1;
	for (int v:g[u])
		if (v!=f) {
			dfs1(v,u); size[u]+=size[v]; //统计当前节点的子树大小
			if (size[v]>size[son[u]]) son[u]=v; //记录重儿子
		}
}

dfs2

这次我们需要处理出以下数组:

  • \(top_u\) :\(u\) 所在的重链的链顶
  • \(id_u\) : \(u\) 节点在 dfs 序中的编号

不过这次 dfs 时我们要先遍历重儿子,再遍历轻儿子,原因后面再说

Code

void dfs2(int u,int topf) //topf:u所在的重链的链顶
{
	top[u]=topf,id[u]=(++cnt);
	if (son[u]) dfs2(son[u],topf); //注意这里要先判断当前节点是否为叶节点
	for (int v:g[u])
		if (!top[v])
			dfs2(v,v); //轻儿子所在的重链的链顶就是它本身
}

树上操作

由于 dfs2 时是先遍历的重儿子,再遍历的轻儿子,那么树上每条重链的 \(id\) 都是连续的

由于是 dfs ,那么子树内的 \(id\) 也是连续的

这样就方便用数据结构维护了

处理子树

处理 \(x\) 子树:

直接在数据结构上处理 \([id_x,id_{x+size_x-1}]\) 区间就行了

代码就不放了

处理树上路径

处理树上 \(x\) 到 \(y\) 的路径:

我们可以先把路径分成两部分:\(x\) 到 \(LCA(x,y)\) 和 \(y\) 到 \(LCA(x,y)\)

我们再回想下求 \(LCA\) 的最朴素的写法:就是只要 \(x\) 和 \(y\) 不相等就选择最深的节点往上跳

那么这里我们也用类似的做法:只要 \(x\) 和 \(y\) 不在同一条重链里,就选择重链链顶深度高的那个跳到链顶的父节点,同时在处理它到链顶的这条链,\(x\) 和 \(y\) 在同一条重链后还要再处理 \(x\) 到 \(y\) 的这条链

Code

void solve(int x,int y)
{
	while (top[x]!=top[y]) {
		if (depth[top[x]]<depth[top[y]]) swap(x,y);
		//处理线段树上 id[top[x]]~id[x] 区间
    	x=fa[top[x]];
	}
	if (depth[x]>depth[y]) swap(x,y);
	//处理线段树上 id[y]~id[y] 区间
}

处理边权问题

先把边权存到边连接的深度更大的点上,再进行处理

但是处理树上路径时,当 \(top_x=top_y\) 后,应该改成处理 \([id_x+1,id_y]\) 区间

因为此时 \(x\) 就为原来的 \(x\) 和 \(y\) 的 LCA 了,由于 LCA 上的点权存的是 \(fa_{LCA}\) 到 LCA 的边权,就不在 \(x\) 到 \(y\) 的路径上了

推荐练习题

标签:son,剖分,笔记,id,fa,重链,节点,size
From: https://www.cnblogs.com/guoxiangyu66/p/17190034.html

相关文章

  • 狂神说css学习笔记
    什么是CSSCascadingStyleSheet层叠级联样式表CSS:表现层(美化网页)如:字体,颜色,边距,高度,宽度,背景图片,网页定位,网页浮动等。CSS发展史CSS1.0CSS2.0DIV(块)+CSS,HTML和CSS......
  • 2023爬虫学习笔记 -- m3u8视频下载
    一、目标地址https://www.XXXX.com/二、获取mu38文件1、点击XHR,刷新页面,会看到这里有两个m3u8文件2、将m3u8地址复制到浏览器,会自动下载下来,index内容如下mixed内容如下3、......
  • 人月神话阅读笔记之一
    这段时间看了老师推荐的《人月神话》,这不是我第一次听闻这本书,当初的概论课上就有听老师说起过这本书,如今终于是第一次上手了。作者在书中的第一章——焦油坑,给我们讲述......
  • Python学习笔记(八)列表与元组
    一、列表的创建示例:1#列表中的元素可以是任意数据类型2li=[1,2,3,4,'张三','李四']3print(li)4li1=[]#空列表用于存放数据5#list()中必须是可......
  • 卡特兰数学习笔记
    参考了这篇博客引入\(n\)个元素进栈序列为:\(1,2,3,4...n\),求总共有多少种出栈序列。将进栈表示为\(+1\),出栈表示为\(-1\),则\(1,3,2\)的出栈序列可以表示为:\(+1,-1,......
  • Java官方笔记1编写运行Java程序
    你可能已经迫不及待想安装Java,写个Java程序跑起来了。但是在这之前,有些概念需要提前了解,因为Java跟C、C++和Python都有点不一样。编译和执行我们在文本文件中编写英文代......
  • C++笔记-指针
    1.const指针和指向const的指针指向const的指针是在类型前加星号可以指向非const类型指针可以改变指向dereference不能改变值const指针是在类型后面加星号指针不可......
  • C++笔记-static本地变量
    static本地变量只能被本地看到,所以不同函数之间的static变量相同也没事,但是同一个函数调用多次会忽略后面的初始化。#include<iostream>voidmyStaticFunction(){......
  • C++笔记-函数指针
    函数指针语法://fcnPtrisapointertoafunctionthattakesnoargumentsandreturnsanintegerint(*fcnPtr)();特点:函数指针的类型(参数和返回值)都必须和......
  • python操作pandas的笔记
    importpandasaspddata={'name':['Alice','Bob','Charlie','David'],'age':[25,30,35,40],'gender':['F','M','M','M'......