简单树问题

多叉树

定义

  1. *有n个结点,n-1条边的连通无向图

  2. 无向无环的连通图

  3. 任意两个结点之间有且仅有一条简单路径的无向图

  4. 任何边均为桥的连通图

桥

连接两个连通块的边

也就是说

删除桥后,图上的连通块个数+1
  1. 没有环,且在任意不同两点间添加一条边之后所得图含唯一的一个环的图

简单来说

  1. 连通图

  2. 无向图

  3. 边比点少一条

  4. 无环

存储方式

  1. 只储存父结点编号fa[i]

    对于任意树,因为树上每个结点仅有一个父结点

    仅储存最低信息

    只能从叶子结点向上,不能向下

    仅使用一个数组

  2. 图论存储方式(邻接表,链式前向星)

    多用于多叉树

    可以从根结点DFS整棵树

  3. 二叉树存储方式

    左儿子是2i,右儿子是2i+1

树的遍历

为了访问树的所有结点

二叉树:前中后序遍历

多叉树:

进行DFS 扫一遍

利用了树的递归性质:

在遍历子树前,将要下放的信息,由当前结点下放到子树

在便利子树后,将子树的信息合并到当前结点

BFS应用较少,通常需要同一深度的结点一起处理时使用

Q:是不是和深度就要使用BFS???

A:NONONO

A:虽然说DFS不是严格按照深度进行遍历

  但是我们可以在不同的深度进行不同的处理
  
  BFS一是要在“同一深度的结点一起处理时使用”

A:况且DFS比BFS要好写哇

例题

P5018 [NOIP2018 普及组] 对称二叉树

一句好神奇又不得不承认的一句话

万物皆可哈希

哈希:比较两个值是否相等
    X      Y
   / \    / \
  Lx Rx  Ly Ry

这是两棵树

那么就会有

Hash1(x)=(Lx*b^2+Rx*b+x)%p

Hash1(y)=(Ly*b^2+Ry*b+y)%p

其中,b为大于1000的质数,p随机取

如果当这两棵子树完全相等的时候

即:Lx=Ly,Rx=Ry,X=Y

那么Hash(x)=Hash(y)

我们只要判断Hash(x)和Hash(y)是否相等,就可以知道两棵子树是否相等

这个过程是递归的

所以说,举个栗子,Lx=Hash(Lx)

因为是对称的,所以我们要再弄一个函数,来存储对称的值

Hash1(x)=(Rx*b^2+Lx*b+x)%p

Hash1(y)=(Ry*b^2+Ly*b+y)%p

然后比较Hash1(X)与Hash2(Y)是否相等

然后这题比较玄学的就是

你打个40分的暴力,交上去竟然是正解

对于DFS,主要消耗复杂度的地方在于搜索失败返回的地方

暴力:

从根结点开始,依次判断每个点是否为对称二叉树的根

如果两个结点不一样,就返回

如果当前子树是对称二叉树,则忽略其他子树

假如,我们在搜索一棵树,并且刚刚枚举了k个点出现了不一样的点

就说明左右子树至少有k个结点

那么问题就变成了两个不小于k的,且和为n的子问题

设左右字数比较小的那一个有p个结点

那么我们相当于将一个规模为p(k≤p<n/2)和规模为n-p的问题合并为一个规模为n的问题

整棵树的复杂度表示为f(n)

那么f(n)=f(p)+f(n-p)+k≤f(p)+f(n-p)+p

第二个是启发式合并的复杂度

启发式合并在合并一个大小为n和一个大小为m的两棵子树的时候(n<m)

复杂度是O(n)

也就是较小的那一个

此题同理

所以此题的复杂度最坏不会大过启发式合并

也就是小于O(nlogn)

此题范围常数较小

所以暴力一样可以过

感觉可以拿去当题解

最小生成树

树

有n个点,n-1条边的连通图

(树的性质有许多,这里先说一个)

两棵树通过一条边相连所得的图还是树

生成树指一个图的边集的一个为树的子集

最小生成树即指所有树的子集中权值(权值和)最小的

常用贪心策略

Kruskal

(基于边的贪心)

  1. 先将所有边安从小到大的顺序排序(一般使用快排),并认为每一个点都是孤立的

  2. 按从小到大的顺序枚举每一条边

    如果这条边连接着两个点不在一个连通块,那么就把这条边加入最小生成树,并将两个连通块合并
    
     如果这条边连接的两个点在一个连通块,就跳过。直到选取了n-1条边为止
    
  3. 使用并查集维护数的连通性,复杂度 \(O(mlogm+mk)\) \((k\) 为并查集常数 \()\)

证明

(反证法)

假如用Kruskal生成的最小生成树是错误的

图中有一个更小的

那么图中就会有一条边比最小生成树中的一条边要小

但是这条边是不存在的

因为我们的边是从小到大枚举的

所以,如果没有枚举到这条边,就说明他不存在

就算出现了也只能是和它相等

因为在枚举的时候相等的两条边先后顺序不大一样

可能会有另外一条边与其相等

但是,这并不影响最终的权值和

所以,我们还可以得出另外一个结论:

在有多条边的权值相等的时候,最小生成树不唯一

Prim

(基于点的贪心)

  1. 将点分为两类 ,已选择和未选择的,初始化所有点都是未选择的(没有被选入最小生成树)

  2. 随机挑选一个点作为初始点

  3. 从所有连接两种类型的点的边,选取一条权值最小的,将对应边加入最小生成,将对应点点加入已选择的

  4. 复杂度取决于如何选择最小边(采用什么结构来维护)

    暴力选取复杂度为\(O(n^2+m)\)

    二叉堆维护为\(O((n+m)logn)\)

    斐波那契堆维护为\(O(m+logn)\)

很像Dij

没理解可以不用理解(doge

Prim \(VS\) Kruskal

两个算法有着近似的理论复杂度,但是Prim常数更大

使用二叉堆优化的Prim跑不过Kruskal

虽然使用斐波那契堆的Prim复杂度比Kruskal杀了一个logm,但斐波那契堆没有STL实现手写 几乎不可行,且Kruskal中logm进位排序中使用,常数较小反而还快

在稠密图 \((m≈n^2)\) 中暴力的Prim有着最好的时间复杂度,但是同样不一定跑得过Kruskal

并且Kruskal比Prim更易理解,代码更简单

所以在OI中,常用的是Kruskal

所以Prim没理解没关系

树形DP

DP通常用子问题推到更大的问题

树本身字数就是树的子结构,具有一样的性质

树形DP通常采用子树推到当前结点的方向,层层向上推导

求一个树,每个子树内所有结点的最大值,其实就是一种简单的树形DP

树的LCA

最近公共祖先 简称LCA(Lowest Common Ancestor)

两个结点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个(离他们最近的那个)

性质

任意两个结点和他们的LCA相连形成一条路径,这条路径是该两点在树上的唯一路径(树上任意两点仅有一条路径)

同时LCA为该路径中深度最浅的点

怎么找两个点的LCA

朴素求法
  1. 平高度

    如果两个结点的高度不相等,就要与比较浅的那个高度平齐

    因为LCA为该路径中深度最浅的点

    所以LCA只能比浅的点更浅,或者就是比较浅的那个点,但一定不能使深的那个点

  2. 向上跳

    同时减少他们的高度

  3. 同一点

    找他们的公共祖先,直到找到为止

树的直径

树中最长的一条边(不唯一,可以有很多条)

  • 从当前根找一个深度最大的点x,以x为根找到离x最远的点,这两点的连线即为数的一条之境

  • DP求出每个点对影响下的链最长有多长,每个点取子树最大值和次大值,表示该节点为直径LCA时的最大长度

树的重心

使节点数最多的子树的结点最小的根为树的重心(最多两个)

树的重心可以用DFS模拟定义求解

等价条件:

以树的重心为根时,所有子树的大小都不超过整棵树大小的一半

等价于 

如果一个结点为根时,所有子树的大小都不超过整棵树的大小的一般,该结点为重心

注意:自己父结点也是一棵子树

(性质)重心为根时,所有点到重心的和最小

void dfs(int x)
{
	siz[x]=1;//大小
	for(int i=first[x];i;i=nxt[i])
	{
		int v=to[i];
		if(v==fa[x]) continue;
		fa[v]=x;
		dfs(v);//孩子结点
		siz[x]+=siz[v];
		w[x]=max(w[x],siz[v]);//计算最大的子树 
	}
	w[x]=max(w[x],n-siz[x]);//不要忘记他的父亲也是一颗子树 
	if(w[x]<=n/2)//树的重心不会大于数的一半 
		ans[ans[0]!=0]=x;//因为树的重心最多有两个,所以如果ans[0]上已经存了一个重心了,那么就存在ans[1]里 
}
终于写完惹……
点个赞再走叭awa
posted @ 2021-08-15 20:52  晨曦时雨  阅读(172)  评论(0)    收藏  举报
-->