首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

关于虚树

编程知识
2024年09月29日 10:08

关于虚树

\(\large 9.29\ upd:\)
更新了二次排序。

瞎扯

某些树上问题,给了巨多节点,而实际上它们之中只有小部分能做出贡献,其余都是些水军,为杀尽 OIers的脑细胞 做出努力

考虑重新种一棵树,浓缩信息,简化节点个数,于是产生了 虚树

大概是长这个样子:
红色结点是我们选择的关键点,即能够做出贡献的点。红色和黑色结点都是虚树中的点。黑色的边是虚树中的边。

因为任意两个关键点的 LCA 也是需要保存重要信息的,能够维持树的形态,所以我们需要保存它们的 LCA

显然,在保证爹不会变成儿子,儿子不会变成爹 爷爷也不行 的前提下,我们是可以随便把原有的点添到虚树中去的

你当然可以把原树所有的点都加到虚树中,只不过你这虚树建了跟建了一样

因此,为了方便,我们可以首先将 \(1\) 号节点加入虚树中,并且不会影响答案

构造

构造有两种方式,其中二次排序的方法常数较大,但是好记,也好理解。

另一种,则是借助单调栈实现的。

随个人喜好吧~

二次排序

很直观的思路。

首先将关键点按 \(DFS\) 序排序,枚举每个相邻的关键点,两两求得 \(LCA\) 并加入序列 \(\mathcal A\) 中。

此时序列 \(\mathcal A\) 中包含了虚树中的所有点,但是由于关键点的 \(LCA\) 可能相同,所以仍需去重。

因此我们把序列 \(\mathcal A\) 按照 \(DFS\) 序 从小到大排序并去重。

最后,在序列 \(\mathcal A\) 上,枚举相邻的两个点 \(x,y\),求得它们的 \(LCA\) 并且连接 \(LCA(x,y),y\),即可。

Code

int dfn[N];
int a[N],m,A[N],len;//m为关键点个数

bool cmp(int x,int y){
	return dfn[x]<dfn[y];
} 

void build(){
	sort(a+1,a+1+m,cmp);
	
	for(int i=1;i<m;i++){
		A[++len]=a[i];
		A[++len]=lca(a[i],a[i+1]);
	}
	A[++len]=a[m];
	
	sort(A+1,A+1+len,cmp);
	len=unique(A+1,A+1+len)-A-1;
	
	for(int i=1;i<len;i++){
		addnew(lca(A[i],A[i+1]),a[i+1]);
	}
}

单调栈

直接枚举所有关键点对,暴力求解 LCA 的时间复杂度显然不可接受。

我们可以将所有关键点先按 DFS 序排序,按顺序一个一个加到树里。刚开始树上只有 \(1\) 号点。

接下来,我们用一个栈维护在树上一条链上的所有点。这个栈内的所有点满足其 DFS 序单调递增。

每次要将下一个关键点(设为 \(u\))入栈前,求一下当前栈顶元素和这个关键点 \(u\) 的LCA \(p\),分讨:

  • 如果栈顶是 \(p\),则可以知道我们加入的关键点 \(u\) 和栈中的点在一条链上,直接将关键点加入栈中即可。如图

  • 如果此时栈顶不是 \(p\)(显然这时候 \(p\) 的 DFS 序比栈顶大,且 栈顶所在位置的子树业已处理完毕),说明 \(p\) 不在链上,将栈顶弹出,直到栈顶为 \(p\),此时插入关键点。
    弹栈的时候记得把他和他父亲的边连一下。

    左子树出栈

酱紫我们的虚树就种好了捏~

Code

bool cmp(int x,int y){
	return dfn[x]<dfn[y];
}

int stk[N],top;
void build(){
	sort(a+1,a+1+k,cmp);
	stk[top=1]=1;
	for(int i=1;i<=k;i++){
		if(top==1){
			stk[++top]=a[i];
		}
		int lca=LCA(a[i],stk[top]);
		if(lca==stk[top]) continue;
		while(top>1 && dfn[stk[top-1]]>=dfs[lca]){
			addnew(stk[top-1],stk[top]);
			top--;
		}
		if(lca!=stk[top]){
			addnew(lca,stk[top]);
			stk[top]=lca;
		}
		stk[++top]=a[i];
	}
	while(top>0){//别忘了把栈里的连边
		addnew(stk[top-1],stk[top]);
		top--;
	}
	return;
}

例题

P2495 [SDOI2011] 消耗战

思路

把资源丰富的点看作关键点,建虚树。
对于阳历:

对于询问4 5 7 8 3构建出的虚树

考虑在虚树上DP。

\(f(u)\) 表示切断 \(u\) 的子树中的所有点的代价,\(mn(u)\) 表示从 \(u\) 到根节点的路径上最小的边权

分两种情况

如果 \(u\) 上边有资源,那么不管子树怎么样,\(u\)都要与根节点分离,即\(f(u)=mn(u)\)

否则就是 \(min(mn(u),\sum_{v=son[u]}f(v))\)

Code

这里是用单调栈实现的。

Elaina's Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rd read()
#define mkp make_pair
#define psb push_back
#define Elaina 0
#define random(a,b) (1ll*rand()*rand()*rand()%((b)-(a)+1)+(a))
inline int read(){
	int f=1,x=0;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) f=(ch=='-'?-1:1);
	for(;isdigit(ch);ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
	return f*x;
}
const int mod=998244353;
const int N=1e6+100;
const int inf=0x7fffffff7fffffff;

int n,m,k,a[N];

//原树
int h[N],idx;
struct EDGE{
	int to,nxt,w;
}e[N];

void add(int x,int y,int z){
	e[++idx]=(EDGE){y,h[x],z};
	h[x]=idx;
}

//虚树
int h1[N],idx1;
struct EDGE1{
	int to,nxt;
}e1[N];

void addnew(int x,int y){
	e1[++idx1]=(EDGE1){y,h1[x]};
	h1[x]=idx1;
}


int cnt,f[N],dfn[N],siz[N],son[N],topf[N],dep[N];
int mn[N];
void dfs1(int x,int fa){
	siz[x]=1;
	f[x]=fa;
	for(int i=h[x];i;i=e[i].nxt){
		int to=e[i].to;
		if(to==fa) continue;
		dep[to]=dep[x]+1;
		mn[to]=min(mn[x],e[i].w);
		dfs1(to,x);
		siz[x]+=siz[to];
		if(siz[to]>siz[son[x]]) son[x]=to;
	}
}

void dfs2(int x,int topfa){
	topf[x]=topfa;
	dfn[x]=++cnt;
	if(!son[x]) return ;
	dfs2(son[x],topfa);
	for(int i=h[x];i;i=e[i].nxt){
		int to=e[i].to;
		if(!topf[to]){
			dfs2(to,to);
		}
	}
}

int LCA(int x,int y){//树剖LCA
	while(topf[x]!=topf[y]){
		if(dep[topf[x]]<dep[topf[y]]) swap(x,y);
		x=f[topf[x]];
	}
	if(dep[x]<dep[y]) swap(x,y);
	return y;
}

bool cmp(int x,int y){
	return dfn[x]<dfn[y];
}

int stk[N],top;
void build(){//建虚树
	sort(a+1,a+1+k,cmp);
	stk[top=1]=1;
	for(int i=1;i<=k;i++){
		if(top==1){
			stk[++top]=a[i];
		}
		int lca=LCA(a[i],stk[top]);
		if(lca==stk[top]) continue;
		while(top>1 && dfn[stk[top-1]]>=dfn[lca]){
			addnew(stk[top-1],stk[top]);
			top--;
		}
		if(lca!=stk[top]){
			addnew(lca,stk[top]);
			stk[top]=lca;
		}
		stk[++top]=a[i];
	}
	while(top>0){
		addnew(stk[top-1],stk[top]);
		top--;
	}
	return;
}

int dp[N];
int DP(int x){
	if(h1[x]==0) return mn[x];
	int ans=0;
	for(int i=h1[x];i;i=e1[i].nxt){
		int to=e1[i].to;
		ans+=DP(to);
	}
	h1[x]=0;
	return dp[x]=min(ans,1ll*mn[x]);
}

signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	
	mn[1]=1ll<<60;
	n=rd;
	for(int i=1;i<n;i++){
		int x=rd,y=rd,z=rd;
		add(x,y,z),add(y,x,z);
	}
	dep[1]=1;
	dfs1(1,0);
	dfs2(1,1);
	m=rd;
	while(m--){
		k=rd;
		for(int i=1;i<=k;i++) a[i]=rd;
		build();
		printf("%lld\n",DP(1));
	}
	
	return Elaina;
}

就是说该吃饭了对吗? 饿。

From:https://www.cnblogs.com/Elaina-0/p/18350221
本文地址: http://www.shuzixingkong.net/article/2392
0评论
提交 加载更多评论
其他文章 【翻译】实现 Blocked Floyd-Warshall 用于解决所有对最短路径问题 C# 实现
介绍 在之前的帖子中,我们实现了Floyd-Warshall(弗洛伊德-沃沙尔算法)(四种变体)以及路由重建算法。在这些帖子中,我们探讨了所有对最短路径问题的基本概念、内存中的数据表示、并行性、向量化以及如何将算法调整为适应数据特性。 在本帖中,我们将继续我们的旅程,探索一种更高效的方法来解决所有对
【翻译】实现 Blocked Floyd-Warshall 用于解决所有对最短路径问题 C# 实现 【翻译】实现 Blocked Floyd-Warshall 用于解决所有对最短路径问题 C# 实现 【翻译】实现 Blocked Floyd-Warshall 用于解决所有对最短路径问题 C# 实现
利用 Page Visibility API 优化网页性能与用户体验
在现代 Web 开发中,用户可能会频繁切换标签页,或让网页处于后台运行。为了避免不必要的资源浪费并提升用户体验,合理利用 Page Visibility API 可以在页面不可见时暂停或减少资源的消耗,并在页面重新可见时恢复正常操作。 在这篇博客中,我将展示如何通过 Page Visibility
SpringBoot+Docker +Nginx 部署前后端项目
部署SpringBoot项目(通关版) 一、概述 使用 java -jar 命令直接部署项目的JAR包和使用Docker制作镜像进行部署是两种常见的部署方式。以下是对这两种方式的概述和简要的优劣势分析: 1.1、使用 java -jar 命令直接部署项目的JAR包 概述: 通过 java -jar
SpringBoot+Docker +Nginx 部署前后端项目 SpringBoot+Docker +Nginx 部署前后端项目 SpringBoot+Docker +Nginx 部署前后端项目
从零开始学机器学习——线性和多项式回归
首先给大家介绍一个很好用的学习地址:https://cloudstudio.net/columns 在之前的学习中,我们已经对数据的准备工作以及数据可视化有了一定的了解。今天,我们将深入探讨基本线性回归和多项式回归的概念与应用。 如果在过程中涉及到一些数学知识,大家也不必感到畏惧,我会逐步为大家进行
从零开始学机器学习——线性和多项式回归 从零开始学机器学习——线性和多项式回归 从零开始学机器学习——线性和多项式回归
.NET 跨平台工业物联网网关解决方案
前言 随着工业4.0时代的到来,物联网技术正在以前所未有的速度改变着我们的生产和生活方式。本文给大家介绍一个基于 .NET 6 开发的跨平台工业物联网网关解决方案。 工业物联网(IIoT)成为了连接物理世界与数字世界的纽带。而在这个网络中,工业物联网网关就像是一个智能的交通警察,负责指挥着设备与云端
.NET 跨平台工业物联网网关解决方案 .NET 跨平台工业物联网网关解决方案 .NET 跨平台工业物联网网关解决方案
深入理解 Nuxt.js 中的 app:data:refresh 钩子
title: 深入理解 Nuxt.js 中的 app:data:refresh 钩子 date: 2024/9/29 updated: 2024/9/29 author: cmdragon excerpt: 摘要:本文详细介绍了 Nuxt.js框架中的app:data:refresh钩子,包括其定义
深入理解 Nuxt.js 中的 app:data:refresh 钩子 深入理解 Nuxt.js 中的 app:data:refresh 钩子
将 LLMs 精调至 1.58 比特: 使极端量化变简单
随着大语言模型 (LLMs) 规模和复杂性的增长,寻找减少它们的计算和能耗的方法已成为一个关键挑战。一种流行的解决方案是量化,其中参数的精度从标准的 16 位浮点 (FP16) 或 32 位浮点 (FP32) 降低到 8 位或 4 位等低位格式。虽然这种方法显著减少了内存使用量并加快了计算速度,但往
将 LLMs 精调至 1.58 比特: 使极端量化变简单 将 LLMs 精调至 1.58 比特: 使极端量化变简单 将 LLMs 精调至 1.58 比特: 使极端量化变简单
Hugging Face + JuiceFS:多用户多节点环境下提升模型加载效率
Hugging Face 的 Transformers 是一个功能强大的机器学习框架,提供了一系列 API 和工具,用于预训练模型的下载和训练。为了避免重复下载,提高训练效率,Transformers 会自动下载和缓存模型的权重、词表等资源,默认存储在 ~/.cache/huggingface/hu
Hugging Face + JuiceFS:多用户多节点环境下提升模型加载效率