一种神秘的 O(n logn) 建虚树方式
如题,我在 2025.6.20 的模拟赛中,只记得应该按
首先按
注意到此时的第一个点一定是虚树的根,第二个点一定是它的儿子,且第二个点的子树就是后面一段连续的区间。
我赛时的
预处理子树内
容易说明,这样做的时间复杂度是
理论上,这种建树方式的常数主要是递归带来的,比二次排序 + LCA 连边少了
实际测试确实得到了这样的结果(P2495):
- 统一使用倍增 LCA
- 递归建树 2.41s
- LCA 连边 2.93s
- 统一使用欧拉序
O(1) LCA- 递归建树 2.12s
- LCA 连边 2.19s
注意到它甚至比
代码并没有比 LCA 连边复杂多少,太令人感动了()
vector<int> V;
vector<pair<int,int> > E[N];
int rt;
inline bool isanc(int u,int v){
return dfn[u]<=dfn[v] && dfn[v]<=ed[u];
}
inline void dfs(int u){
// 递归建树直到当前队头不在子树内
while(!V.empty() && isanc(u,V.back())){
int v=V.back(),w=getmn(u,v);
V.pop_back();
E[u].emplace_back(mkp(v,w));
dfs(v);
}
}
inline void buildtr(vector<int> &vec){
for(int x:vec) V.emplace_back(x);
V.emplace_back(1);
sort(V.begin(),V.end(),[](int x,int y){return dfn[x]<dfn[y];});
int sz=V.size();
rep(i,1,sz-1) V.emplace_back(lca(V[i-1],V[i]));
sort(V.begin(),V.end(),[](int x,int y){return dfn[x]>dfn[y];});
V.resize(unique(V.begin(),V.end())-V.begin());
// 以上和二次排序一模一样
for(auto x:V) E[x].clear();
rt=V.back();V.pop_back();
dfs(rt);
}
upd:伟大的前队长 xmg 告诉我,这个东西本质是用递归实现单调栈,单调栈建树中弹出栈顶和我的递归回溯本质是相同的。
于是我被偏序了(显然它打不过单调栈),还是太不会虚树了呜呜。
但感觉这个比传统单调栈建虚树好理解一点点,大概递归还是比较自然的想法。
反向思维一下,似乎树上遍历(比如 dp)也可以求