虚树:原树中给一些点,通过一些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