集训 Day 3 总结 虚树 + dfs tree + 基环树

虚树

虚树,顾名思义是 只关注原树上的某些 关键点,在保留原树祖孙关系的前提下建出的一棵边数、点数大大减少的树

适用于优化某些在整棵树上进行 d p dp dp d f s dfs dfs 的问题

通常是题目中出现多次询问,每次给出树上的一些关键点,同时保证 ∑ 关键点数 ≤ n \sum关键点数 \leq n 关键点数n ,很大可能就是建出虚树来处理

概括来说,虚树只进行两步操作 剪掉无用树枝压缩树上路径

Warning

有一个常见误区:压缩树上路径 的含义

在这里插入图片描述

如图 ,只有红色是关键点,黑色加粗为虚树上的点

若是只压缩路径,那么完全可以 1 − > 4 , 1 − > 6 1->4,1->6 1>41>6 连边 ,而不需要保留 4 , 6 4,6 4,6 的 lca 3 3 3 号点

为什么要这样保留呢?实际上,这保证了 虚树上的边对应原树上的路径是不交的

这个性质在后面题目中有大用

思想懂了,来看具体实现

build 建树

通常有两种建树方式,两次 s o r t sort sort 和 单调栈

本人通常采用前者,码量较短

int p[2*N] , rt , len , num ;
void build( int x )
{sort( c[x].begin() , c[x].end() , cmp ) ;num = c[x].size() ;len = 0 ;p[++len] = c[x][0] ;for(int i = 1 ; i < c[x].size() ; i ++ ) {p[++len] = c[x][i] ;p[++len] = LCA( c[x][i-1] , c[x][i] ) ; // 由虚树定义,这样一定能把虚树上的点都包含 }sort( p+1 , p+len+1 , cmp ) ;len = unique( p+1 , p+len+1 ) - (p+1) ; // 再去重 for(int i = 2 ; i <= len ; i ++ ) { // 恰好连了 len-1 条边,且不重复,不成环 int x = p[i] , y = LCA( x , p[i-1] ) ;add( y , x , dep[x]-dep[y] ) ;}rt = p[1] ;
}

基于 d f s dfs dfs 序的性质,可以保证建出的虚树是正确的,需要注意 p p p 数组需要开 2 2 2

从代码里我们也可以得到 虚树上的点数上界为关键点的 2 倍,是线性级别

例题

A

To在这里插入图片描述
直接在原树上跑 n n n d f s dfs dfs 显然会超时

所以建出虚树后直接 d f s dfs dfs 统计即可

#include<bits/stdc++.h>
using namespace std ;typedef long long LL ;
const int N = 2e5 + 100 ; int n , a[N] ;
vector<int> E[N] , c[N] ;int dep[N] , fat[N][22] , dfn[N] , tim ;
void dfs( int x , int fa )
{dep[x] = dep[fa] + 1 , fat[x][0] = fa , dfn[x] = ++tim ; for(int i = 1 ; i <= 20 ; i ++ ) fat[x][i] = fat[fat[x][i-1]][i-1] ;for(int t : E[x] ) {if( t == fa ) continue ;dfs( t , x ) ;}
}
int LCA( int x , int y )
{if( dep[x] < dep[y] ) swap( x , y ) ;for(int i = 20 ; i >= 0 ; i -- ) {if( dep[fat[x][i]] >= dep[y] ) x = fat[x][i] ;}if( x == y ) return x ;for(int i = 20 ; i >= 0 ; i -- ) {if( fat[x][i] != fat[y][i] ) x = fat[x][i] , y = fat[y][i] ;}return fat[x][0] ;
}
bool cmp ( int x , int y )
{return dfn[x] < dfn[y] ;
}struct nn
{int lst , to , val ;
}e[N] ;
int head[N] , tot ;
inline void add( int x , int y , int v )
{e[++tot] = (nn){ head[x] , y , v } ;head[x] = tot ;
}
int p[2*N] , rt , len , num ;
void build( int x )
{sort( c[x].begin() , c[x].end() , cmp ) ;num = c[x].size() ;len = 0 ;p[++len] = c[x][0] ;for(int i = 1 ; i < c[x].size() ; i ++ ) {p[++len] = c[x][i] ;p[++len] = LCA( c[x][i-1] , c[x][i] ) ; // 由虚树定义,这样一定能把虚树上的点都包含 }sort( p+1 , p+len+1 , cmp ) ;len = unique( p+1 , p+len+1 ) - (p+1) ; // 再去重 for(int i = 2 ; i <= len ; i ++ ) { // 恰好连了 len-1 条边,且不重复,不成环 int x = p[i] , y = LCA( x , p[i-1] ) ;add( y , x , dep[x]-dep[y] ) ;}rt = p[1] ;
}
LL ans ;
int siz[N] , col ;
void calc( int x , int fa )
{if( a[x] == col ) siz[x] = 1 ;else siz[x] = 0 ;for(int i = head[x] ; i ; i = e[i].lst ) {int t = e[i].to ;if( t == fa ) continue ;calc( t , x ) ;siz[x] += siz[t] ;ans += 1LL*e[i].val*siz[t]*(num-siz[t]) ;}head[x] = 0 ;
}int main()
{scanf("%d" , &n ) ;int x , y ;for(int i = 1 ; i < n ; i ++ ) {scanf("%d%d" , &x , &y ) ;E[x].push_back( y ) ;E[y].push_back( x ) ;}dfs( 1 , 0 ) ;for(int i = 1 ; i <= n ; i ++ ) {scanf("%d" , &a[i] ) ;c[a[i]].push_back( i ) ;}for(int i = 1 ; i <= n ; i ++ ) {if( c[i].size() <= 1 ) continue ;col = i ; tot = 0 ;build( i ) ;calc( rt , 0 ) ;}printf("%lld\n" , ans ) ;return 0 ;
}

B

To

在这里插入图片描述
先考虑原树上 d p dp dp ,好转移

放到虚树上,由于虚树上一条边代表了一段路径,我们钦定它断开时显然应该找原树这一段权值最小的一条边

预处理之

int dep[N] , fat[N][22] , Min[N][22] , dfn[N] , tim ;
void dfs( int x , int fa )
{dep[x] = dep[fa] + 1 , fat[x][0] = fa , dfn[x] = ++tim ;for(int i = 1 ; i <= 20 ; i ++ ) {fat[x][i] = fat[fat[x][i-1]][i-1] ;Min[x][i] = min( Min[x][i-1] , Min[fat[x][i-1]][i-1] ) ;// 预处理}for(int i = head[x] ; i ; i = E[i].lst ) {int t = E[i].to ;if( t == fa ) continue ;Min[t][0] = E[i].val ;dfs( t , x ) ;}
}
int LCA( int x , int y )
{if( dep[x] < dep[y] ) swap( x , y ) ;for(int i = 20 ; i >= 0 ; i -- ) {if( dep[fat[x][i]] >= dep[y] ) x = fat[x][i] ;}if( x == y ) return x ;for(int i = 20 ; i >= 0 ; i -- ) {if( fat[x][i] != fat[y][i] ) x = fat[x][i] , y = fat[y][i] ;}return fat[x][0] ;
}int b[N] , p[2*N] , K , len , rt ;
bool is[N] ;
bool cmp ( int x , int y )
{return dfn[x] < dfn[y] ;
}
int Get( int x , int y )
{int res = 1e9 ;for(int i = 20 ; i >= 0 ; i -- ) {if( dep[fat[x][i]] >= dep[y] ) {res = min( res , Min[x][i] ) ;x = fat[x][i] ;}}return res ;
}
void build()
{sort( b+1 , b+K+1 , cmp ) ;len = 0 ; p[++len] = 1 , p[++len] = b[1] ;for(int i = 2 ; i <= K ; i ++ ) {p[++len] = b[i] ;p[++len] = LCA( b[i-1] , b[i] ) ; }sort( p+1 , p+len+1 , cmp ) ;len = unique( p+1 , p+len+1 ) - (p+1) ;rt = p[1] ;for(int i = 2 ; i <= len ; i ++ ) {int x = p[i] , y = LCA( p[i-1] , p[i] ) ;add1( y , x , Get(x,y) ) ;}
}
LL f[N] ;
void calc( int x , int fa )
{if( is[x] ) f[x] = LL(1e17) ;else f[x] = 0 ;for(int i = Hd[x] ; i ; i = e[i].lst ) {int t = e[i].to , v = e[i].val ;if( t == fa ) continue ;calc( t , x ) ;f[x] += min( f[t] , 1LL*v ) ;}Hd[x] = 0 ;
}
// each queryscanf("%d" , &K ) ;for(int j = 1 ; j <= K ; j ++ ) scanf("%d" , &b[j] ) , is[b[j]] = 1 ;tot1 = 0 ;build() ;calc( rt , 0 ) ;if( f[rt] >= LL(1e17) ) {printf("-1\n") ;}else printf("%lld\n" , f[rt] ) ;for(int j = 1 ; j <= K ; j ++ ) is[b[j]] = 0 ;

C

To 充分利用虚树性质
在这里插入图片描述
这道题可以告诉我们:虚树本身是有非常多的性质的!

考虑建出了包含关键点的虚树之后,讨论一下所有点到最近关键点的情况:

在这里插入图片描述
对于被 “剪掉的树枝” (蓝色部分):显然需要先走到被虚树包含 (被压缩的 或 本身就是虚树上节点) 的点上,

对于被 压缩的节点 (如 5 5 5 号点) :一定与所在虚树边的两端点中的一个情况相同,可以从深度较大的往上二分得到分界点

剩下虚树上的点,我们显然可以直接跑多源最短路,把所有关键点放堆里跑一次

然后就是一些 简单(烦人)分讨啦

D

To

题如其名,十分毒瘤,码量较大

E

To

一道不太一样的 “虚树” 题

发现本题实际上是要 动态维护虚树  ( LCT ? 不会

有一个下位替代:用 s e t set set 来维护

具体来说: s e t set set 中只存关键点,按 d f n dfn dfn 序排序

首先一个经典结论:树上若干个点的 L C A LCA LCA 等价于 d f n dfn dfn 序 最小和最大的两点的 L C A LCA LCA

这样我们就可以实时找到这个连通块的根了,再利用 d f s dfs dfs 序的性质能够实现部分操作

对于本题,还有一个结论

DFS 序求出后,假设关键点按照 DFS 序排序后是 a 1 , a 2 , . . . , a k {a_1,a_2,...,a_k} a1,a2,...,ak
那么所有关键点形成的极小连通子树的边权和的两倍等于 d i s ( a 1 , a 2 ) + d i s ( a 2 , a 3 ) + . . . + d i s ( a k , a 1 ) dis(a_1,a_2)+dis(a_2,a_3)+...+dis(a_k,a_1) dis(a1,a2)+dis(a2,a3)+...+dis(ak,a1)

如果是点权,那么要取 相邻两点路径上除它们 L C A LCA LCA 以外的点权和,这样求和结果是 除整个连通块的 L C A LCA LCA 外,所有点点权的 2 2 2

画图理解

那么本题维护一下插入删除时的贡献变化就做完了

最后再总结一下虚树注意事项:

  1. 用两次 s o r t sort sort 建虚树时要注意去重前的点数是 2 n 2n 2n 的,数组要开够

dfs tree

顾名思义,在 有向/无向图 中从某个点开始,按 D F S DFS DFS 的顺序遍历,每个点只经过一次,形成的一棵树

处理图上问题有很大作用,如 T a r j a n Tarjan Tarjan 算法实际上就是基于 d f s t r e e dfs tree dfstree

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/44591.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

人为因素:为什么网络安全不仅仅关乎技术

关注公众号网络研究观获取更多最新内容。 我们生活在一个生活与技术日益紧密交织的世界。但在构建防火墙和安装防病毒软件时&#xff0c;我们常常会忘记一个关键因素&#xff1a;人的行为。 网络犯罪分子正是利用了人为因素&#xff0c;利用巧妙的心理战术绕过最强大的安全措…

【MySQL基础篇】事务

事务简介 事务是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 典型事例&#xff1a;银行转账操作 假设张三向李四进行转账…

防火墙NAT、智能选路综合实验

一、实验拓扑 二、实验要求 1&#xff0c;办公区设备可以通过电信链路和移动链路上网(多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换) 2&#xff0c;分公司设备可以通过总公司的移动链路和电信链路访问到Dmz区的http服务器 3&#xff0c;多出口环境基于带宽比例…

node js安装、配置(Windows版)

目录 node js 安装 node js 全局配置 1、全局安装路径 2、全局缓存路径 3、修改环境变量 pnpm安装、卸载 全局安装pnpm 验证pnpm版本 卸载pnpm 1、移除全局安装的包 2、移除pnpm cli 脚本直接安装 npm安装的使用命令直接卸载 node js 安装 cmd 查看是否存在&…

容器docker 架构命令案例

文章目录 前言一、docker1.1 为什么有docker1.2 docker架构1.3 docker 安装1.4 docker中央仓库1.5 docker 基本指令1.6 docker数据卷&#xff0c;挂载例&#xff1a;nginx 数据卷挂载例&#xff1a;mysql 本地持久化 1.7 镜像制作镜像结构dockerfile基础指令容器生成镜像 1.8 d…

宿主机访问docker容器中的mysql被拒绝

问题&#xff1a; 解决方案&#xff1a; 1.进入docker中的mysql容器 docker exec -it 容器名称/id /bin/bash 2.登录用户 mysql -u root -p 3.进去mysql自带的管理数据库mysql use mysql; 4.查询用户的访问权限 SELECT user, host FROM user WHERE userroot;5.发现该用…

绘画平台小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;学生管理&#xff0c;讲师管理&#xff0c;课程类型管理&#xff0c;课程信息管理&#xff0c;课程购买管理&#xff0c;作业类型管理 开发系统&#xff1a;Windows 架构模式&#xff1a;SSM JDK版本&…

AURORA仿真

AURORA 仿真验证 定义&#xff1a;AURORA是一种高速串行通信协议&#xff0c;通常用于在数字信号处理系统和其他电子设备之间传输数据。它提供了一种高效的方式来传输大量数据&#xff0c;通常用于需要高带宽和低延迟的应用中。AURORA协议通常由Xilinx公司的FPGA器件支持&#…

golang 项目打包部署环境变量设置

最近将 golang 项目打包部署在不同环境&#xff0c;总结一下自己的心得体会&#xff0c;供大家参考。 1、首先要明确自己目标服务器的系统类型(例如 windows 或者Linux) &#xff0c;如果是Linux 还需要注意目标服务器的CPU架构(amd或者arm) 目标服务器的CPU架构可执行命令&…

python 爬取当当网图书榜

首先查看当当网好评书单页面&#xff0c;找到翻页的URL参数 直接用requests请求页面 resp requests.get(url) 找到想要的信息&#xff0c;使用正则表达式把这些信息提取出来 patternre.compile(list_num.*?(\d).<.*?<img src"(.*?)".*?title"(.*?…

Eel入门还有一些案例

Eel入门还有一些案例 Eel 是一个 Python 库&#xff0c;它允许 Python 程序通过简单的 API 与网页进行交互。它使用 WebSocket 协议来实现 Python 后端和 JavaScript 前端之间的实时通信。下面是关于 Eel 的用法、通信原理和使用场景的一篇博客文章。 Eel的基本原理 Eel的基本原…

FGF14:脑部疾病新潜力靶标

成纤维细胞生长因子14&#xff08;FGF14&#xff09;是FGF11亚家族成员&#xff0c;在神经元的所有基本特性&#xff08;内在放电、兴奋性和抑制性神经元的突触传递和可塑性&#xff09;中发挥作用。 &#xff08;数据来源AlphaFold&#xff09; FGF14由247个氨基酸组成&#x…

实战篇(九):解锁3D魔方的秘密:用Processing编程实现交互式魔方

解锁3D魔方的秘密:用Processing编程实现交互式魔方 使用 Processing 创建一个 3D 魔方效果展示1. 安装 Processing2. 项目结构3. 代码实现4. 代码解释4.1. 初始化魔方4.2. 绘制魔方4.3. 处理鼠标事件4.4. 检查点击的面4.5. 旋转面和最终确定旋转5. 运行和测试6. 细节解释6.1. …

【资源调度】2-如何解决资源调度问题?

导读&#xff1a;本期是全网最全【资源调度】系列推文的第2期(共50期左右)。上期我们在《何为调度&#xff1f;》中&#xff0c;对调度的定义与作用、计划与调度的关系、调度问题的拆解做了详细介绍。从本期开始&#xff0c;我们选择【客服调度】场景作为【资源调度】问题的具象…

51单片机(STC8H8K64U/STC8051U34K64)_RA8889_8080参考代码(v1.3)

硬件&#xff1a;STC8H8K64U/STC8051U34K64 RA8889开发板 硬件跳线变更为并口8080模式&#xff0c;PS00x&#xff0c;R143&#xff0c;R142不接&#xff0c;R141无关 8080接口电路连接图&#xff1a; 实物连接图&#xff1a; RA8889开发板外接MCU连接器之引脚定义&…

IMS架构中的注册与会话流程:RTPEngine集成及消息路由详解

目录 S-CSCF 调用 RTPengine 整体路由 注意 IMS 注册流程 和 IMS 会话流程 的区别 IMS注册流程 IMS会话流程(如INVITE请求) 这种设计的原因 P-CSCF 调用 RTPengine S-CSCF 调用 RTPengine 整体路由 UA a生成SDP offer&#xff0c;发送SIP INVITE请求(包含SDP offer)&…

核密度估计KDE和概率密度函数PDF(深入浅出)

目录 1. 和密度估计&#xff08;KDE&#xff09;核密度估计的基本原理核密度估计的公式核密度估计的应用Python中的KDE实现示例代码 结果解释解释结果 总结 2. 概率密度函数&#xff08;PDF&#xff09;概率密度函数&#xff08;PDF&#xff09;是怎么工作的&#xff1a;用图画…

RDNet实战:使用RDNet实现图像分类任务(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整策略设置混合精度&#xff0c;DP多卡&#xff0c;EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看测试完整的代码 在上…

AI大模型走进汽车车机,智驾将是未来

车机里的AI大模型在汽车行业中的应用越来越广泛&#xff0c;主要体现在智能座舱和自动驾驶系统的深度融合上。通过将AI大模型应用于车机系统&#xff0c;可以实现更高智能化的人车交互体验。AI大模型作为人工智能发展的核心引擎&#xff0c;正在成为汽车智能化发展的关键之一。…

基于颜色模型和边缘检测的火焰识别FPGA实现,包含testbench和matlab验证程序

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 将FPGA仿真结果导入到matlab显示结果&#xff1a; 测试样本1 测试样本2 测试样本3 2.算法运行软件版本 vivado2019.2 …