有时,我们只需要知道树上一些关键点组合而成的信息,就可以知道答案。
可以建出这些点的虚树。
虚树有静态和动态两种。
静态虚树就是网上讲的普通虚树。
按照dfs序从小到大添加节点,每次更新虚树。
维护一个栈,表示最右链。
根据lca分类讨论。
实际上这就是模拟dfs的过程。
所以如果要树dp,在建虚树的过程中可以顺便完成,减少代码量。
建立静态虚树的代码如下:
void ins(int x){
if(!tp){
st[++tp]=x;
return;
}
int lc=lca(st[tp],x);
if(lc==st[tp])st[++tp]=x;
else{
while(tp>1&&id[st[tp-1]]>=id[lc]){
g[st[tp-1]].push_back(st[tp]);
tp--;
}
if(lc!=st[tp]){
g[lc].push_back(st[tp]);
st[tp]=lc;
}
st[++tp]=x;
}
}
void bd(vector<int>va){
tp=0;
sort(va.begin(),va.end(),cp);
auto it=unique(va.begin(),va.end());
va.erase(it,va.end());
for(int i=0;i<va.size();i++)
ins(va[i]);
for(int i=2;i<=tp;i++)
g[st[i-1]].push_back(st[i]);
}
实际上,如果询问数较少,可以直接按照定义建出虚树。时间复杂度(O(n)),(n)为点数。
动态虚树基于如下结论:
如果把所有点排序,排序后数组设为a。
把dis[lca(a[i],a[i+1])]和dis[lca(a[n],a[1])]加起来,得到了链并(虚树大小)的两倍。
注意链并是边权。
([SDOI2015]寻宝游戏)
实际上求链并还可以永久标记线段树+轻重链剖分。但是和正解没有什么关系。
两种虚树分别有擅长/不擅长的领域。
动态虚树基于一个公式,在一些题目中这个公式可以方便维护。
而且有动态加点只能动态虚树。
但是如果要知道虚树的形态,只能用静态虚树。
虚树还有一个离线排序的技巧。
每次建虚树要排序,这样子时间复杂度是nlogn的。
但是如果没有强制在线,可以使用n个链表b,b[i]存储dfs序为i的节点。
离线后从小到大遍历b,把b的元素插回去即可。
静态虚树例题:
[SDOI2018]战略游戏
[SDOI2011]消耗战
mx的仙人掌(没做)
xr2 永恒
WC2018 通道
暴力写挂(没用虚树做)
ccADJLEAF2(想出来了,没做)
NOI2018 情报中心
CF1336F(和情报中心差不多,没做)
[GDOI2019]颜色(想出来了,没做)
[HNOI2014]世界树
[SDOI2019]世界地图(未完全理解)
jzoj5058/洛谷 树上游戏
[HNOI2018]毒瘤
[SDOI2017]天才黑客
河童重工(没做)
动态虚树例题:
[SDOI2015]寻宝游戏
[ZJOI2019]语言
bzoj七彩树(看懂了题解,没做)
动态半平面交(七彩树强化版)