虚树

【什么是虚树】

OI-wiki

如果题目里有一些关键点,而整棵树规模过大,我们可以考虑使用虚树来重新建树,以减少树的规模。

虚树:一颗树,树中包含所有关键点,以及任意多个关键点的 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:

  1. LCA 就是栈顶,则它们在一条链上,直接把这个关键点加入栈中。

  2. 否则,一直 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;
}
posted @ 2024-02-01 09:44  FLY_lai  阅读(9)  评论(0编辑  收藏  举报