集训 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 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

taro小程序terser-webpack-plugin插件不生效(vue2版本)

背景 最近在做公司内部的小程序脚手架&#xff0c;为了兼容老项目和旧项目&#xff0c;做了vue2taro,vue3taro两个模板&#xff0c;发现terser-webpack-plugin在vue2和vue3中的使用方式并不相同&#xff0c;同样的配置在vue3webpack5中生效&#xff0c;但是在vue2webpack4中就…

【C++】哈希(散列)表

目录 一、哈希表的基本概念1.哈希的概念2.哈希冲突2.1 哈希函数2.2 哈希冲突的解决办法2.2.1 闭散列2.2.2 开散列 二、哈希表的实现1.闭散列的实现1.1 闭散列的结构1.2 闭散列的插入1.3 闭散列的删除1.4 闭散列的查找 2.开散列的实现2.1 key值不能取模的情况2.2 开散列的结构2.…

编译x-Wrt 全过程

参考自;​​​​​​c编译教程 | All about X-Wrt 需要详细了解的小伙伴还请参看原文 ^-^ 概念&#xff1a; x-wrt&#xff08;基于openwrt深度定制的发行版本&#xff09; 编译系统: ubuntu22.04 注意&#xff1a; 特别注意的是&#xff0c;整个编译过程&#xff0c;都是用 …

线程池笔记

笔记梳理 前言.PHONYC标准库头文件C/C通用或C特有头文件mkdirc_str()snprintfvsnprintfumaskopen函数可变参数列表va_startva_endfunctionalstatic_castpthread_cond_init_threads.emplace_backstd::bindstd::placeholdersThreadPool(const ThreadPool<T> &tp) dele…

springboot系列教程(三):全局异常映射(含源码)

一、异常分类 这里的异常分类从系统处理异常的角度看&#xff0c;主要分类两类&#xff1a;业务异常和系统异常。 1、业务异常 业务异常主要是一些可预见性异常&#xff0c;处理业务异常&#xff0c;用来提示用户的操作&#xff0c;提高系统的可操作性。常见的业务异常提示&…

学会电子期刊制作的必备工具

​随着数字化时代的到来&#xff0c;电子期刊作为一种新型的传播媒介&#xff0c;已经越来越受到大众的青睐。它以环保、便捷、互动性强等特点&#xff0c;逐渐成为传统纸质期刊的重要补充。那么&#xff0c;如何制作一款精美的电子期刊呢&#xff1f;本文将为你介绍学会电子期…

电子电气架构 --- 关于DoIP的一些闲思 上

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…

什么? CSS 将支持 if() 函数了?

CSS Working Group 简称 CSSWG, 在近期的会议中决定将 if() 添加到 CSS Values Module Level 5 中。 详情可见&#xff1a;css-meeting-bot 、[css-values] if() function 当我看到这个消息的时候&#xff0c;心中直呼这很逆天了&#xff0c;我们知道像 less 这些 css 这些预…

给 「大模型初学者」 的 LLaMA 3 核心技术剖析

编者按&#xff1a; 本文旨在带领读者深入了解 LLaMA 3 的核心技术 —— 使用 RMSNorm 进行预归一化、SwiGLU 激活函数、旋转编码&#xff08;RoPE&#xff09;和字节对编码&#xff08;BPE&#xff09;算法。RMSNorm 技术让模型能够识别文本中的重点&#xff0c;SwiGLU 激活函…

传输层重点协议

目录 一、TCP协议 TCP协议段落格式 原理 1、确认应答机制 2、超时重传机制 3、连接管理机制 三次握手 四次挥手 &#xff08;1&#xff09;不能合并为三次挥手的原因 &#xff08;2&#xff09;延时应答机制—实现合并 &#xff08;3&#xff09;TIME_WAIT的作用 &…

代码随想录——不同路径Ⅱ(Leetcode 63)

题目链接 动态规划 class Solution {public int uniquePathsWithObstacles(int[][] obstacleGrid) {int m obstacleGrid.length;int n obstacleGrid[0].length;int[][] dp new int[m][n];// 遇到障碍则从(0,0)到达for(int i 0; i < m && obstacleGrid[i][0] …

基于Vue和UCharts的前端组件化开发:实现高效、可维护的词云图与进度条组件

基于Vue和UCharts的前端组件化开发&#xff1a;实现高效、可维护的词云图与进度条组件 摘要 随着前端技术的迅速发展和业务场景的日益复杂&#xff0c;传统的整块应用开发方式已无法满足现代开发的需求。组件化开发作为一种有效的解决方案&#xff0c;能够将系统拆分为独立、…

浏览器出现 502 Bad Gateway的原理分析以及解决方法

目录 前言1. 问题所示2. 原理分析3. 解决方法 前言 此类问题主要作为疑难杂症 1. 问题所示 2. 原理分析 502 Bad Gateway 错误表示服务器作为网关或代理时&#xff0c;从上游服务器收到了无效的响应 通常出现在充当代理或网关的网络服务器上&#xff0c;例如 Nginx、Apache…

Go 语言返回组装数据

文章id 文章标题 ..... 分类 字段 &#xff1a;[分类名&#xff0c;分类描述 .... ]标签字段 : [标签名, 标签id ..... ]type ArticleWithCategoryLabel struct {system.SysArticleCategoryName system.SysCategorie json:"category_name"LabelName system.SysLab…

2024年7月13日全国青少年信息素养大赛Python复赛小学高年级组真题

第一题 题目描述 握情况。他决定让每个人输入一个正整数 N (0≤N≤1000)&#xff0c;然后计算并输出(5*N)的值。请用 在一个神秘的王国里&#xff0c;国王希望通过一个简单的测试来评估他的子民对基 础数学运算的掌 Python 编写程序&#xff0c;程序执行后要求用户输入一个正…

50、haproxy+keepalive+nginx

keepalivehaproxy 客户端&#xff1a;192.168.168.21haproxy1&#xff1a;192.168.168.43haproxy2&#xff1a;192.168.168.44vip&#xff1a;192.168.168.100nginx1:192.168.168.31nginx2:192.168.168.32haproxykeepalive做高可用nginx做后台haproxy1haproxy2一起操作&#x…

121. 小红的区间翻转(卡码网周赛第二十五期(23年B站笔试真题))

题目链接 121. 小红的区间翻转&#xff08;卡码网周赛第二十五期&#xff08;23年B站笔试真题&#xff09;&#xff09; 题目描述 小红拿到了两个长度为 n 的数组 a 和 b&#xff0c;她仅可以执行一次以下翻转操作&#xff1a;选择a数组中的一个区间[i, j]&#xff0c;&#x…

监控房价和挂牌数量的工具-以成都房价为例

介绍 本文将介绍如何通过zervice提供的工具来监控成都房价&#xff08;其他城市或者地区类似&#xff09;&#xff0c;包括价格和挂牌数量。可以对购房一族提供数据参考。 数据来源 数据来源方面&#xff0c;本文以成都为例&#xff0c;我们会使用链家数据-> 选择地图找房…

《Linux系统编程篇》Visual Studio Code配置下载,中文配置,连接远程ssh ——基础篇

引言 vscode绝对值得推荐&#xff0c;非常好用&#xff0c;如果你能体会其中的奥妙的话。 工欲善其事&#xff0c;必先利其器 ——孔子 文章目录 引言下载VS Code配置VS Code中文扩展连接服务器 连接服务器测试确定服务器的IP地址VS code 配置ssh信息选择连接到主机选择这个添…

【JVM实战篇】内存调优:内存泄露危害+内存监控工具介绍+内存泄露原因介绍

文章目录 内存调优内存溢出和内存泄漏内存泄露带来什么问题内存泄露案例演示内存泄漏的常见场景场景一场景二 解决内存溢出的方法常用内存监控工具Top命令优缺点 VisualVM软件、插件优缺点监控本地Java进程监控服务器的Java进程&#xff08;生产环境不推荐使用&#xff09; Art…