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

news2024/9/20 22:38:50

虚树

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

适用于优化某些在整棵树上进行 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 query
	scanf("%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.coloradmin.cn/o/1923457.html

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

相关文章

唯一索引插入引发的死锁问题

MySQL 8.4.0 RR隔离级别 场景复现 分析下面SQL死锁的场景 对于switch表&#xff0c;有主键索引id和唯一索引(uid、type)。 CREATE TABLE switch (id int NOT NULL AUTO_INCREMENT COMMENT Unique identifier for each switch,uid int NOT NULL COMMENT User ID associated wi…

Autoware 定位之基于ARTag的landmark定位(六)

Tip: 如果你在进行深度学习、自动驾驶、模型推理、微调或AI绘画出图等任务&#xff0c;并且需要GPU资源&#xff0c;可以考虑使用UCloud云计算旗下的Compshare的GPU算力云平台。他们提供高性价比的4090 GPU&#xff0c;按时收费每卡2.6元&#xff0c;月卡只需要1.7元每小时&…

AI大模型来了,低代码还有机会吗?

AI大模型的突飞猛进&#xff0c;不仅引领了技术的革新浪潮&#xff0c;也为各行各业的发展带来了前所未有的挑战与机遇。近年来&#xff0c;随着人工智能技术的不断进步&#xff0c;关于各行各业将被AI取代的论调此起彼伏&#xff0c;引发了许多从业者的不安。 几年前&#xf…

Flowable-流程图标与流程演示

BPMN 2.0是业务流程建模符号2.0的缩写。它由Business Process Management Initiative这个非营利协会创建并不断发展。作为一种标识&#xff0c;BPMN 2.0是使用一些符号来明确业务流程设计流程图的一整套符号规范&#xff0c;它能增进业务建模时的沟通效率。目前BPMN2.0是最新的…

AI人工智能填词,唱响心中独特旋律

在音乐的无垠宇宙中&#xff0c;每个人的内心都有一段独一无二的旋律在悄然回荡。而如今&#xff0c;人工智能填词正以其神奇的力量&#xff0c;帮助我们将这些深藏心底的旋律化作动人的歌词&#xff0c;让它们得以放声歌唱。 “妙笔生词智能写歌词软件&#xff08;veve522&am…

【QT】布局管理器

布局管理器 布局管理器1. 垂直布局2. 水平布局3. 网格布局4. 表单布局5. Spacer 布局管理器 之前使⽤ Qt 在界⾯上创建的控件, 都是通过 “绝对定位” 的⽅式来设定的&#xff1b;也就是每个控件所在的位置, 都需要计算坐标, 最终通过 setGeometry 或者 move ⽅式摆放过去。 …

一文彻底学会Vue3路由:全面讲解路由流程、路由模式、传参等——全栈开发之路--前端篇(7)路由详解

全栈开发一条龙——前端篇 第一篇&#xff1a;框架确定、ide设置与项目创建 第二篇&#xff1a;介绍项目文件意义、组件结构与导入以及setup的引入。 第三篇&#xff1a;setup语法&#xff0c;设置响应式数据。 第四篇&#xff1a;数据绑定、计算属性和watch监视 第五篇 : 组件…

pytorch-pytorch之LSTM

目录 1. nn.LSTM2. nn.LSTMCell 1. nn.LSTM 初始化函数输入参数与RNN相同&#xff0c;分别是input_size&#xff0c;hidden_size和num_layer foward函数也与RNN类似&#xff0c;只不过返回值除了out外&#xff0c;ht变为(ht,ct) 代码见下图&#xff1a; 2. nn.LSTMCell 初…

SQL优化-索引

什么是索引&#xff1f; 索引&#xff08; index &#xff09;是帮助 MySQL 高效获取数据的数据结构 ( 有序 ) 。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用&#xff08;指向&#xff09;数据&#xff0c; 这…

这是我见过最棒的大模型干货!!!

大模型技术的发展和迭代2024年已经可以按天来计算了&#xff0c;几乎每天都有新的大模型和技术登场。 从基座模型Mamba2&#xff0c;Jamaba&#xff0c;到Dora&#xff0c;LoftQ&#xff0c;GaLore等最新的微调技术&#xff1b;KTO&#xff0c;IPO&#xff0c;SimPO等微调技术…

STM32实战篇:按键(外部输入信号)触发中断

功能要求 将两个按键分别与引脚PA0、PA1相连接&#xff0c;通过按键按下&#xff0c;能够触发中断响应程序&#xff08;不需明确功能&#xff09;。 代码流程如下&#xff1a; 实现代码 #include "stm32f10x.h" // Device headerint main() {//开…

ZGC的流程图

GC标记过程 1、初始标记 扫描所有线程栈的根节点&#xff0c;然后再扫描根节点直接引用的对象并进行标记。这个阶段需要停顿所有的应用线程&#xff08;STW&#xff09;&#xff0c;但由于只扫描根对象直接引用的对象&#xff0c;所以停顿时间很短。停顿时间高度依赖根节点的数…

鸿蒙HarmonyOS应用开发为何选择ArkTS不是Java?

前言 随着智能设备的快速发展&#xff0c;操作系统的需求也变得越来越多样化。为了满足不同设备的需求&#xff0c;华为推出了鸿蒙HarmonyOS。 与传统的操作系统不同&#xff0c;HarmonyOS采用了一种新的开发语言——ArkTS。 但是&#xff0c;刚推出鸿蒙系统的时候&#xff0…

uni-app 保存号码到通讯录

1、 添加模块 2、添加权限 3、添加策略 Android&#xff1a; "permissionExternalStorage" : {"request" : "none","prompt" : "应用保存运行状态等信息&#xff0c;需要获取读写手机存储&#xff08;系统提示为访问设备上的照片…

Prometheus + alermanager + webhook-dingtalk 告警

添加钉钉机器人 1. 部署 alermanager 1.1 下载软件包 wget https://github.com/prometheus/alertmanager/releases/download/v0.26.0/alertmanager-0.26.0.linux-amd64.tar.gz 网址 &#xff1a;Releases prometheus/alertmanager (github.com) 1.2 解压软件包 mkdir -pv …

用 Kotlin 编写四则运算计算器:从零开始的简单教程

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

电-气阀门定位器YT-1000系列产品说明

电-气阀门定位器YT-1000系列 使用注意事项 • 搬运安装或使用中对产品过大的震动或撞击会成为产品故障的原因。 • 超过规定参数范围使用也会成为产品故陷的原因。 • 不使用的气路接口要用堵塞堵住。 • 不使用产品而长时间放悝在室外时,要盖上产品外壳以免雨水进入产品…

八款主流电脑监控软件推荐|2024年最佳电脑监控软件排行榜

在现代社会中&#xff0c;电脑监控软件已经成为企业和家庭不可或缺的工具。无论是为了确保员工的工作效率&#xff0c;还是保护孩子在互联网上的安全&#xff0c;这些软件都能提供有力的支持。本文将为大家介绍2024年最受欢迎的八款电脑监控软件。 1. 固信软件 固信软件是一款综…

服务重启时容器未自动启动

1、容器重启策略 通过设置容器的重启策略&#xff0c;‌可以决定在容器退出时Docker守护进程是否重启该容器。‌常见的重启策略包括&#xff1a;‌ no&#xff1a;‌不重启容器&#xff0c;‌默认策略。‌always&#xff1a;‌无论容器是如何退出的&#xff0c;‌总是重启容器…

2024年公共文化与社会服务国际会议(ICPCSS 2024)

2024年公共文化与社会服务国际会议 2024 International Conference on Public Culture and Social Services 【1】会议简介 2024年公共文化与社会服务国际会议是一个集学术性、实践性和国际性于一体的盛会。我们期待与您共同探讨公共文化与社会服务的未来发展方向&#xff0c;为…