虚树模板详解和例题

虚树:原树中给一些点,通过一些lca把它们重新连接在一起建成一棵新树,相当于对树进行了化简。新树边上的信息可以用树链剖分/倍增等维护。

如何建虚树:

假设给出的点都存在d数组里。

对于这些点,按照它们在原树中的dfs序从小到大排个序。

然后我们建一个栈,从栈底到栈顶相当于是一条当前虚树根一直往下走的链。

遍历d数组,假设当前要加入的点是v,栈顶的点是u,

1.lca(u,v)==u 显然u还可以往下走,这条链还没完,直接把v入栈,continue。

2.lca(u,v)!=u 这代表着u这颗子树已经结束了,v是u子树外的点。设lca(u,v)=w,我们沿着当前维护的这条链一直往上走,在栈上相当于是一个弹栈+建边的过程,代表着弹出的点往下的那条链已经结束了,不会再被更改,直到有一个点是w或w的祖先为止。此时如果这个点是w则直接把v入栈,反之则先把w入栈,再把v入栈。

注意,入栈时不建边而在出栈时建,就是为了出栈时链才成型的特性。

把dfs序从小到大排序,相当于我们是在dfs树上中序遍历了这些点,保证了算法的正确性。

板子是我自己yy的,过了以下的两题,如果在其他题目有锅,欢迎指出qwq。

bzoj2286消耗战(板子题,可以用来检验建树模板是否正确)

#include<bits/stdc++.h>
using namespace std;
#define rep(x,y,z) for (register int x=y; x<=z; x++)
#define downrep(x,y,z) for (register int x=y; x>=z; x--)
#define ms(x,y,z) memset(x,y,sizeof(z))
#define LL long long
#define repedge(x,y) for (register int x=hed[y]; ~x; x=edge[x].nex)
#define repE(x,y) for(register int x=head[y]; ~x; x=E[x].nex)
inline int read(){
	int x=0; int w=0; char ch=0;
	while (ch<'0' || ch>'9') w|=ch=='-',ch=getchar();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return w? -x:x;
}
const int N=250005;
const int Maxlg=18;
const int inf=N+1;
int n,m,tin[N],tout[N],tot,dep[N],toc[N],sum,f[N][Maxlg+1],g[N][Maxlg+1];
int nedge,hed[N],Nedge,head[N],d[N],imp[N],st[N];
LL dp[N];
struct Edge{ int to,nex; LL cst; }edge[N<<1],E[N<<1];
void addedge(int a,int b,int c){
	edge[nedge].to=b; edge[nedge].nex=hed[a];
	edge[nedge].cst=c; hed[a]=nedge++;
}
void dfs_1(int k){
	tin[k]=++tot;
	repedge(i,k){
		int v=edge[i].to; if (v==f[k][0]) continue;
		f[v][0]=k; g[v][0]=edge[i].cst; dep[v]=dep[k]+1;
		dfs_1(v);
	}tout[k]=tot;
}
void dfs_2(int k){
	dp[k]=0; toc[++sum]=k;
	repE(i,k){
		int v=E[i].to; dfs_2(v); 
		if (!imp[v]) dp[k]+=min(dp[v],E[i].cst);
		else dp[k]+=E[i].cst;
	}
}
int cmp(int a,int b){ return (tin[a]<tin[b]); }
bool isancestor(int x,int y){ return ((tin[x]<=tin[y])&&(tout[y]<=tout[x])); }
int getlca
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值