虚树【学习笔记】

news/2024/9/22 21:27:11/文章来源:https://www.cnblogs.com/zhagnxinyue/p/18332874

为什么要用虚树?

例题

在某些树上问题中,对于某次询问,我们并不需要用到全部的树上的点:

例如,例题中:

总点数 \(n \le 2.5\times10^5\)
询问次数 \(m \le 5\times10^5\)
询问的点数 \(\sum k_i \le 5\times10^5\)

我们可以发现其实每次询问均摊下来的询问点数k并不多,但如果每次询问都用到全部的点,会超时

所以我们将所有的关键点拎出来建树,来确保时间复杂度的优秀

朴素做法

我们回到例题上来,可以想到如果树的点数很少时,我们可以直接用 \(DP\)

首先我们设某次询问中被选中的点(资源丰富)为 关键点
\(dp_i\) 表示不让 \(i\)\(i\) 的子树内任意一个关键点互通所需要的最小代价
\(w_{u,v}\) 表示连接 \(u\)\(v\) 的边权
\(u\) 表示 \(i\) 连接的一个儿子节点
转移方程式:

  • \(u\) 是关键点时 : 你必须砍掉 \(i\)\(u\) 的这条边

\(dp_i+=w_{i,u}\)

  • \(u\) 不是关键点时 :你可以选择砍掉 \(i\)\(u\) 的这条边或者\(u\) 不连接关键点

\(dp_i+=min(w_{i,u},dp_u)\)

此时时间复杂度为 \(O(nq)\) 肯定过不了,考虑用虚树建一颗更简洁的树(没有那么多用不到的点)

虚树做法

在原树中,我们可以发现大多数点是没用的,以下图为例:

如果我们选取的关键点是2,4:

图中只有两个红色的点是关键点,而别的点全都是非关键点,对于这道题来说,我们只需要保证 1 号节点无法到达2,4就行了而 1 号节点的右子树没有一个关键点,我们没必要去DP它

观察题目给出的条件,红色点(关键点)的总数是与 n 同阶的,也就是说实际上一次询问中红色的点对于整棵树来说是很稀疏的,所以如果我们能让复杂度由关键点的总数来决定就好了

所以我们需要浓缩信息,只存储与答案相关的信息,把一整颗大树浓缩成小树

虚树长什么样?

这里我们主要通过一些图理解(感谢oiwiki我才不用画图)

下图中,红色结点是我们选择的关键点,红色和黑色结点都是虚树中的点(要把某些红色节点相连必须用到黑色节点),黑色的边是虚树中的边。

因为任意两个关键点的 \(lca\) 也是需要保存重要信息的,所以我们需要保存它们的 \(lca\),因此虚树中不一定只有关键点。

怎么构造虚树?

这里介绍的是二次排序+ lca 连边的方法,还有一种单调栈的构造方法,详见oiwiki

  1. 将关键节点按照 \(dfs\)序 排序,并插入序列 \(a\)
  2. 关键节点中两两求 \(lca\),插入序列 \(a\)
  3. 在将序列 \(a\) 按照 \(dfs\)序 排序,并去重
  4. 遍历序列 \(a\) ,枚举相邻两个点的编号(设为 \(x\)\(y\) )求 \(lca\) ,建一条由 \(lca\) 指向 \(y\) 的边

为什么连接 \(LCA(x,y)\)\(y\) 可以做到不重不漏呢?

证明:

如果 \(x\)\(y\) 的祖先,那么 \(x\) 直接到 \(y\) 连边。因为 \(dfs\)序保证了 \(x\)\(y\)\(dfs\)序是相邻的,所以 \(x\)\(y\) 的路径上面没有关键点。

如果 \(x\) 不是 \(y\) 的祖先,那么就把 \(lca(x,y)\) 当作 \(y\) 的的祖先,根据上一种情况也可以证明 \(lca(x,y)\)\(y\) 点的路径上不会有关键点。

所以连接 \(lca(x,y)\)\(y\),不会遗漏,也不会重复。

另外第一个点没有被一个节点连接会不会有影响呢?因为第一个点一定是这棵树的根,所以不会有影响,所以总边数就是 \(m-1\) 条。

因为至少要两个实点才能够召唤出来一个虚点,再加上一个根节点,所以虚树的点数就是实点数量的两倍。

时间复杂度 \(O(klog_n)\),其中 \(k\) 为关键点数,\(n\) 为总点数。

实现:

int dfn[maxn]
int h[maxn], a[maxn], cnt;  // 存储关键点
bool cmp(int x,int y){return dfn[x]<dfn[y];
}
void buid{h[++k]=1;//为了方便,我们首先将1号节点加入虚树中 sort(h+1,h+1+k,cmp);//操作1,按照dfs序排序 for (int i=1; i<=k; i++) {a[++cnt]=h[i];//将关键点插入序列a if (i==k) break;//操作2,两两求lca插入序列a中 a[++cnt]=lca(h[i],h[i+1]);}sort(a+1,a+1+cnt,cmp);//操作3,排序 cnt=unique(a+1,a+1+cnt)-(a+1);//去重 for (int i=1; i<cnt; i++) {int lc=lca(a[i],a[i+1]);add(lc,a[i+1],0);//操作4,连一条由lca(x,y)指向y的边 }
}

回到例题

虚树建好后,这道题就很好攻克了

\(miv_i\) 表示 \(i\) 到 1 号节点边权最小的一条边(容易理解的是:割掉这条边后,\(i\) 就不再与 1 号节点相连了)
\(col_i\)记录 \(i\) 是否为关键点(是关键点为1,否则为0)

  • \(miv\) 和一些其他数组的预处理
void dfs1(int x,int fa){vis[x]=1;dfn[x]=++cnt;for (int i=he[x];i;i=ne[i])if (!vis[to[i]]){d[to[i]]=d[x]+1;f[to[i]][0]=x;miv[to[i]]=min(miv[x],w[i]);dfs1(to[i],x);}he[x]=0;
}
  • 求解让 1 号节点不与( \(x\)\(x\) 的子树中的关键点)连通的最小代价
int dfs2(int x,int fa){int tmp=0,ans;for (int i=he[x];i;i=ne[i])tmp+=dfs2(to[i],x);if (col[x]) ans=miv[x];else ans=min(miv[x],tmp);he[x]=0;col[x]=0;//多次询问,可以在递归中直接清空 return ans;
}
完整代码(*╹▽╹*)
#include<bits/stdc++.h>
#define int long long
#define pai pair<int,int>
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int maxn=1e6+10;
const int N=30;
const int INF=1e18;
int read(){int x=0,f=1;char c=getchar();while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}while (c>='0'&&c<='9') {x=(x<<1)+(x<<3)+(c^48);c=getchar();}return x*f;
}int tot,n,q,cnt,k,d[maxn];
int miv[maxn],dfn[maxn];
int he[maxn],w[maxn<<1];
int ne[maxn<<1],to[maxn<<1];
int h[maxn],a[maxn];
int f[maxn][N];
bool col[maxn],vis[maxn];void add(int u,int v,int z){ne[++tot]=he[u];he[u]=tot;to[tot]=v;w[tot]=z;
}bool cmp(int x,int y){return dfn[x]<dfn[y];
}void dfs1(int x,int fa){vis[x]=1;dfn[x]=++cnt;for (int i=he[x];i;i=ne[i])if (!vis[to[i]]){d[to[i]]=d[x]+1;f[to[i]][0]=x;miv[to[i]]=min(miv[x],w[i]);dfs1(to[i],x);}he[x]=0;
}void init(){for (int j=1;j<=20;j++)for (int i=1;i<=n;i++)f[i][j]=f[f[i][j-1]][j-1];
}int lca(int x,int y){if (x==y) return x;if (d[x]<d[y]) swap(x,y);for (int j=log2(d[x]);j>=0;j--)if (d[f[x][j]]>=d[y])x=f[x][j];if (x==y) return x;for (int j=log2(d[x]);j>=0;j--)if (f[x][j]!=f[y][j])x=f[x][j],y=f[y][j];return f[x][0];
}int dfs2(int x,int fa){int tmp=0,ans;for (int i=he[x];i;i=ne[i])tmp+=dfs2(to[i],x);if (col[x]) ans=miv[x];else ans=min(miv[x],tmp);he[x]=0;col[x]=0;return ans;
}signed main(){n=read();for (int i=1,x,y,z;i<n;i++){x=read();y=read();z=read();add(x,y,z);add(y,x,z);}d[0]=-INF,miv[1]=INF;dfs1(1,0);init();q=read(); while (q--){k=read();tot=cnt=0;for (int i=1,x;i<=k;i++){x=read();h[i]=x;col[x]=1;}h[++k]=1;sort(h+1,h+1+k,cmp);for (int i=1;i<=k;i++){a[++cnt]=h[i];if (i==k) break;a[++cnt]=lca(h[i],h[i+1]);}sort(a+1,a+1+cnt,cmp);cnt=unique(a+1,a+1+cnt)-(a+1);for (int i=1;i<cnt;i++){int lc=lca(a[i],a[i+1]);add(lc,a[i+1],0);}printf("%lld\n",dfs2(1,0));for (int i=1;i<=cnt;i++)he[i]=0,col[i]=0;}return 0;
}

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

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

相关文章

组合机床与自动化加工技术的网站开发

这两天一直在学习如何排版,开发一个网站,我运用了前几天所学的vue基本语法将网站的大致模样copy下来这期间我查找了不少知识 比如照片如何在网页中不变性的缩放,通过研究我运用class固定结构和class="container"属性将组件固定形成此网页

火山引擎VeDI数据技术分享:两个步骤,为Parquet降本提效

本文将介绍字节跳动基于Parquet格式降本增效的技术原理和在具体业务中的实践,首先介绍了Parquet格式在字节跳动的应用,然后将结合具体的应用场景:小文件合并和列级TTL ,从问题产生的背景和解决问题的技术方案出发,介绍如何基于Parquet格式实现降本增效的目标。更多技术交流…

图片预加载和懒加载

🧑‍💻 写在开头 点赞 + 收藏 === 学会🤣🤣🤣图片预加载和图片懒加载是网页优化的两种常见技术,它们可以提升用户体验并改善网页性能。 图片预加载(Image Preloading): 图片预加载是指在页面加载时提前加载图片,使其缓存在浏览器中,当用户需要查看这些图片时,…

P4062 Yazid 的新生舞会

谨以此文纪念一场灾难 来给这位善良的人的人点点赞首先 题面中所指的众数为 绝对众数(绝对众数是指在一组数据中出现次数 \(超过\) 总数一半的数值。), 下文的所有众数也指绝对众数。 有以下性质任意一个区间的绝对众数的数值唯一如果\(x\)是区间\([l,r]\)的众数,那么对于\…

KubeSphere 部署向量数据库 Milvus 实战指南

作者:运维有术星主Milvus 是一个为通用人工智能(GenAI)应用而构建的开源向量数据库。它以卓越的性能和灵活性,提供了一个强大的平台,用于存储、搜索和管理大规模的向量数据。Milvus 能够执行高速搜索,并以最小的性能损失扩展到数百亿向量。其分布式架构确保了系统的高可用…

利用DYNAMIXEL智能伺服舵机从《传送门2》中打造一个更优质的动画机器人小麦克利(Wheatley)

原文链接:https://www.youtube.com/watch?v=OEn9hZ-Tw1E这段视频由ROBOTIS提供! 大家好,我想给大家推荐一个精彩视频,在视频中展示了如何制作《传送门2》中的动画机器人小麦克利(Wheatley)。看看是如何利用DYNAMIXEL智能伺服系统让小麦克利活起来的。对于那些可能想设计…

GIS视效升级!零代码添加环境效果,支持多种GIS影像协议

在当今的数字化时代,GIS(地理信息系统)不再仅仅只能通过一些二维示意图或简陋的三维地形图表示,它可以通过专业的软件简单升级视效。想象一下,在你的GIS场景中,阳光明媚的天气、突如其来的暴风雨、缭绕的晨雾,统统都可以通过零代码的操作轻松实现,而这个效果我是使用一…

HomeServer平台选择,介绍常用功能

​​ 平台选择 HomeServer 的性能要求不高,以下是我的硬件参数,可供参考: ‍ 硬件:平台:旧笔记本 CPU:i5 4210u 内存 8G 硬盘:128G 固态做系统盘,1T+1T 机械盘组 RAID1 做存储。 硬盘柜:盘位不足使用硬盘柜做拓展,硬盘柜一定要有散热风扇。‍ 待机功耗:笔记本 + 三块…

waf 应用防火墙部署配置

部署方法: 这里拓扑如下:透明部署: 采用接口对的形式部署 从一个口进 从另一个口出 不存在路由交换 可以将设备看做一条线路 这里使用迪普科技的设备进行演示: 首先创建接口对: 基本》接口管理》组网配置》透明模式此时只需要串联设备到线路中即可 这种配置方式不支持负载…

MySQL的binlog日志保存时效设置

若不设置,就会出现上图情况,Data目录体积日益增大。 修改方式:打开C:\ProgramData\MySQL\MySQL Server 8.0下的my.ini,末尾增加: max_binlog_size = 500M expire_logs_days = 5 代表bin log最大体积为500MB,且仅保留5天。 设置之后重新启动MySQL80服务,可以看到效果:…

深度学习中的一些基础函数

激活函数概念 神经网络中每个神经元节点接受上一层神经元的输出值作为本神经元的输入值,并将输入值传给下一层。在多层神经网络中,上层节点的输入在加权求和后与下层节点的输入之间具有一个函数关系,这个函数称为激活函数。激活函数的作用常见激活函数Sigmoid函数单调连续,…

windows下jdk版本切换(bat)

1.jdk下载Oracle官网 https://www.oracle.com/cn/ 资源->下载->Java下载jdk当前最新版本 jdk22版本jdk8版本 当前页面向下拉2.脚本如下: 点击查看代码 @echo off chcp 65001 >nul echo ****************jdk change util************************* echo 此操作需要管理…