首页 > 其他分享 >虚树

虚树

时间:2024-09-09 11:52:45浏览次数:9  
标签:return int void dfs fa 虚树 define

消耗战

首先考虑朴素 \(dp\),设 \(f_u\) 表示使 \(u\) 的子树内的所有关键点都不与 \(u\) 连通的最小代价。如果当前在 \(u\),\(j\) 这个儿子是关键点,那么有转移 \(f_u\leftarrow f_u+val_{u\rightarrow j}\);否则有转移 \(f_u\leftarrow f_u+\min(f_j,val_{u\rightarrow j})\)。

这样做的时间复杂度是 \(O(nm)\) 的,我们考虑优化。

暴力代码:

#include<bits/stdc++.h>
#define int long long
#define N 250005
#define M 500005
using namespace std;
int n,m,h[N],e[M],w[M],ne[M],idx,x[N],f[N];
bool st[N];
void add(int a,int b,int c){
	e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}
void dfs(int u,int fa){
	f[u]=0;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa)continue;
		dfs(j,u);
		if(st[j])f[u]+=w[i];
		else f[u]+=min(f[j],w[i]);
	}
}
signed main(){
	cin>>n;
	memset(h,-1,sizeof h);
	for(int i=1;i<n;i++){
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);add(b,a,c);
	}
	cin>>m;
	while(m--){
		int k;
		cin>>k;
		for(int i=1;i<=k;i++){
			cin>>x[i];
			st[x[i]]=1;
		}
		dfs(1,0);
		cout<<f[1]<<'\n';
		for(int i=1;i<=k;i++){
			st[x[i]]=0;
		}
	}
	return 0;
}

发现 \(\sum k\) 非常小,那么我们能不能把 \(dp\) 的总复杂度控制在 \(O(\sum k)\) 呢?

我们接下来说明如何建虚树:

首先加入所有关键点,然后把他们按照 \(dfs\) 序排序,接着加入相邻两点的 \(lca\),最后对这些点去重,建树。首先这样所有询问的总点数不超过 \(2\times \sum k\),所以复杂度是对的。

那么,为什么这样做是对的呢?考虑两个 \(dfs\) 序相邻的点 \(x,y\) 他们的 \(lca\) 为 \(fa\)。于是我们只需要连接 \(fa\rightarrow y\)。这样是不重不漏的。分类讨论一下:

  • \(x\) 是 \(y\) 的祖先,则 \(fa\) 为 \(x\),因为 \(x,y\) 的 \(dfs\) 序相邻,所以 \(x\rightarrow y\) 上没有其他关键点。

  • \(x\) 不是 \(y\) 的祖先,那么把 \(fa\) 当作 \(y\) 的祖先,\(fa\rightarrow y\) 同上可得没有关键点。

代码:

#include<bits/stdc++.h>
#define int long long
#define N 250005
#define K 20
#define pii pair<int,int>
#define x first
#define y second
using namespace std;
int n,m,k,h[N],a[N],cnt,mn[N];
int dfn[N],tot,f[N];
int fa[N][K],dep[N];
bool st[N];
vector<pii>g[N];
vector<int>e[N];
void add(int a,int b){
	e[a].push_back(b);
}
void add1(int a,int b,int c){
	g[a].push_back({b,c});
}
void dfs(int u,int pre){
	fa[u][0]=pre;
	for(int i=1;i<K;i++){
		fa[u][i]=fa[fa[u][i-1]][i-1];
	}
	dfn[u]=++tot;
	dep[u]=dep[pre]+1;
	for(auto it:g[u]){
		int j=it.x,val=it.y;
		if(j==pre)continue;
		mn[j]=min(mn[u],val);
		dfs(j,u);
	}
}
int get_lca(int a,int b){
	if(dep[a]<dep[b])swap(a,b);
	for(int i=K-1;~i;i--){
		if(dep[fa[a][i]]>=dep[b]){
			a=fa[a][i];
		}
	} 
	if(a==b)return a;
	for(int i=K-1;~i;i--){
		if(fa[a][i]!=fa[b][i]){
			a=fa[a][i];
			b=fa[b][i];
		}
	}
	return fa[a][0];
}
int dp(int u){
	if(e[u].size()==0)return mn[u];
	int sum=0;
	for(auto j:e[u]){
		sum+=dp(j);
	}
	e[u].clear();
	if(st[u])return mn[u];
	else return min(sum,mn[u]);
}
void build(){
	sort(h+1,h+k+1,[&](int x,int y){
		return dfn[x]<dfn[y];
	});
	for(int i=1;i<k;i++){
		a[++cnt]=h[i];
		a[++cnt]=get_lca(h[i],h[i+1]);
	}
	a[++cnt]=h[k];
	a[++cnt]=1;
	sort(a+1,a+cnt+1,[&](int x,int y){
		return dfn[x]<dfn[y];
	});
	cnt=unique(a+1,a+cnt+1)-a-1;
	for(int i=1;i<cnt;i++){
		int lca=get_lca(a[i],a[i+1]);
		add(lca,a[i+1]);
	}
}
signed main(){
	cin>>n;
	memset(mn,0x3f,sizeof mn);
	for(int i=1;i<n;i++){
		int a,b,c;
		cin>>a>>b>>c;
		add1(a,b,c);add1(b,a,c);
	}
	dfs(1,0);
	cin>>m;
	while(m--){
		cin>>k;
		for(int i=1;i<=k;i++){
			cin>>h[i];
			st[h[i]]=1;
		}
		cnt=0;
		build();
		cout<<dp(1)<<'\n';
		for(int i=1;i<=k;i++){
			st[h[i]]=0;
		}
	}
	return 0;
}

标签:return,int,void,dfs,fa,虚树,define
From: https://www.cnblogs.com/zxh923aoao/p/18404060

相关文章

  • 虚树总结
    之前学了一些算法,没有写算法总结,未来会陆续补一些。前置知识:树形\(dp\),\(lca\),\(dfs\)序。我们考虑\([HEOI2014]\)大工程这道题。显而易见,假如这道题只有一次询问,我们可以直接树形\(dp\),快速求出答案,时间复杂度\(O(n)\)。但是,梦想是梦想,现实是现实,这题多组询问,假如一......
  • 虚树
    引入$\color{skyblue}P2495[SDOI2011]消耗战$题意给定一颗$n$个节点的树,每条边有边权。有$m$次询问,每次询问钦定$k$个特殊节点,问使得节点$1$不与任何特殊节点相连通所需要删除的最小总边权是多少。数据范围:对于\(100\%\)的数据,\(2\leqn\leq2.5\ti......
  • 虚树
    虚树VirtualTree浓缩信息,把一整颗大树浓缩成一颗小树。下图中,红色结点是我们选择的关键点。红色和黑色结点都是虚树中的点。黑色的边是虚树中的边。OIWIKI两种建树方式1.第一种构造过程:二次排序+LCA连边(容易理解,常数略大)boolcmp(intx,inty){returndfn[x......
  • 关于虚树
    关于虚树瞎扯某些树上问题,给了巨多节点,而实际上它们之中只有小部分能做出贡献,其余都是些水军,为杀尽OIers的脑细胞做出努力考虑重新种一棵树,浓缩信息,简化节点个数,于是产生了虚树。大概是长这个样子:红色结点是我们选择的关键点,即能够做出贡献的点。红色和黑色结点都是虚树中......
  • 20240806:点分治,虚树选做
    POJ-1741Tree题意:给定一棵树,求多少无序对\((u,v)\)满足\(\text{dist}(u,v)\lek\)。对于树上的路径进行分类:经过根;不经过根。第二类路径点可以通过递归子树求出。对于经过根的路径,可以一遍dfs求出每个点到根的距离\(\text{dis}(u)\)。问题转化为求\(\text{dis}(u)......
  • 虚树【学习笔记】
    为什么要用虚树?例题在某些树上问题中,对于某次询问,我们并不需要用到全部的树上的点:例如,例题中:总点数\(n\le2.5\times10^5\)询问次数\(m\le5\times10^5\)询问的点数\(\sumk_i\le5\times10^5\)我们可以发现其实每次询问均摊下来的询问点数k并不多,但如果每次询问都......
  • 虚树复习 & O(1) 查询 LCA
    放假是不可能做题的。那就写总结把。虚树问题的情境涉及多次树上询问,每次指定一些点,让你计算。此类问题需要我们在线地找到尽可能少的【关键点】进行计算,最好和给的点级别一样。虚树的思想就是这个过程。二次排序一个关键直觉:【指定点】两两的LCA一定是【关键点】。并且......
  • 从 dfs 序求 lca 到虚树到树分块 学习笔记
    前言想象我在口胡三样我都不熟悉的东西并尝试称之为“学习笔记”。其实不过是我自己对于它的一点小理解,甚至可能是错误的!无所谓,口胡!口胡!口胡!口胡!口胡!一些备注\(dfn_u\)为点\(u\)的dfn序,\(nfd_i\)表示第\(i\)个dfs到的点是啥(前者的反数组)dfs序求lca这个很简单,想......
  • 虚树初步学习笔记
    虚树给定一棵树,树上有一些关键点,你要建另一棵树,保留关键点,以及任意一对关键点的\(\text{LCA}\)。当你发现对于一棵树,你只有一些关键点有用的时候,就可以尝试建虚树。两次排序思路先把所有点按\(\text{dfn}\)序排序,然后把\(\text{dfn}\)相邻的两个点取出来,再把它们的\(\t......
  • 虚树
    虚树什么是虚树虚树常常被用在树形\(dp\)中。当一次询问仅仅涉及到整棵树中少量节点时为每次询问都对整棵树进行\(dp\)在时间上是不可接受的。此时,我们建立一棵仅仅包含部分关键节点的虚树,将非关键节点构成的链简化成边或是剪去,在虚树上进行\(dp\)。虚树包含所有的询问点及它们......