虚树
【什么是虚树】
如果题目里有一些关键点,而整棵树规模过大,我们可以考虑使用虚树来重新建树,以减少树的规模。
虚树:一颗树,树中包含所有关键点,以及任意多个关键点的 LCA。
可以发现在这颗虚树中,任意两个关键点的 LCA 都不变(还是对应原树的结点),并且只要我们把虚树中每条边的距离,对应成原树中关键点到 LCA 的距离、LCA 到 LCA 的距离,则任意两个点的距离也不变。
这样我们就可以在虚树上进行想要的操作:例如树形 DP、树上差分、dfn 转线段树等等,不再受规模限制。
【怎么建虚树】
第一种:排序 + LCA
在原树上搞出 LCA,然后先把所有关键点按照 dfn 序排序。
排好之后,把相邻两个关键点的 LCA 求出来。这些点 + 关键点 就包含了所有虚树上的点,但可能有重复。我们把这些点 + 关键点加入一个数组中。
于是我们把这个数组去重,然后再按 dfn 序排序。这个时候再对相邻两个结点 \(x,y\) 求出 \(lca(x,y)\),让 \(lca(x,y)\) 与 \(y\) 连边,即可获得一颗虚树。
这种方法的正确性证明在 OI-wiki 上有。
时间复杂度:如果用 tarjan 求 LCA 可以更快,用倍增就是 \(O(p\log n)\),\(p,n\) 分别为 关键点数、总点数。
第二种:栈
我们用栈维护虚树最右边的一条链。
先把所有关键点按 dfn 排序。初始栈里只加入 \(1\) 号结点。
然后按照 dfn 序依次添加关键点。
每添加一个关键点,求出它与栈顶元素的 LCA:
-
LCA 就是栈顶,则它们在一条链上,直接把这个关键点加入栈中。
-
否则,一直 pop 直到栈顶的 dfn 小于等于 LCA 的 dfn。如果此时栈顶是 LCA,则把这个关键点加入栈中;否则,先加入 LCA,再加入这个点。
因为栈维护的是一条链。只要在入栈时和栈顶连边,就能得到虚树。
排序法代码:
注:最好把虚树的 vector 开在结构体外面,不然可能会炸空间。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n;
vector<int> e[N];
vector<int> g[N];
int num; //关键点个数
vector<int> spc; //关键点
int d[N], fa[N][30], dfn[N];
int cur = 0;
void dfs(int x, int dth, int pr) {
d[x] = dth;
fa[x][0] = pr;
dfn[x] = ++cur;
for (auto i: e[x])
if (i != pr)
dfs(i, dth + 1, x);
}
void init() {
for (int j = 1; j < 30; j++)
for (int i = 1; i <= n; i++)
fa[i][j] = fa[fa[i][j - 1]][j - 1];
}
int lca(int u, int v) {
if (d[u] > d[v])
swap(u, v);
for (int j = 29; j >= 0; j--)
if (d[u] <= d[fa[v][j]])
v = fa[v][j];
if (u == v)
return u;
for (int j = 29; j >= 0; j--)
if (fa[u][j] != fa[v][j])
u = fa[u][j], v = fa[v][j];
return fa[u][0];
}
bool cmp(int a, int b) {
return dfn[a] < dfn[b];
}
struct Virtual_Tree {
int n;
int a[N]; //虚树的点
Virtual_Tree(){}
Virtual_Tree(vector<int> &imp) { //二次排序法
n = 0;
sort(imp.begin(), imp.end(), cmp);
for (int i = 0; i < imp.size(); i++)
a[++n] = imp[i];
for (int i = 0; i < imp.size() - 1; i++)
a[++n] = lca(imp[i], imp[i + 1]);
sort(a + 1, a + n + 1, cmp);
n = unique(a + 1, a + n + 1) - a - 1;
for (int i = 1, lc; i < n; i++) {
lc = lca(a[i], a[i + 1]);
g[lc].push_back(a[i + 1]);
g[a[i + 1]].push_back(lc);
}
}
void dfs(int x, int pr) {
cout << x << ' ' << pr << endl;
for (auto i: g[x])
if (i != pr)
dfs(i, x);
}
} vt;
struct Node {
int a[N];
Node(){}
Node(vector<int> &x) {
// a[0] = x[0];
for (int i = 0; i < x.size(); i++)
a[i] = x[i];
}
} node;
int main() {
cin >> n;
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
cur = 0;
dfs(1, 0, 1);
cin >> num;
for (int i = 1; i <= num; i++) {
int x;
cin >> x;
spc.push_back(x);
}
vt = Virtual_Tree(spc);
vt.dfs(1, 0);
return 0;
}