CF650E Clockwork Bomb

CF650E Clockwork Bomb

题目大意

题目链接

给出两棵 (n) 个节点的树,每次操作可以把第一棵树的一条边删掉然后连另外两个点,且必须满足每次操作完之后仍是一棵树。

问最少需要多少步操作才能把第一棵树变成第二棵树(是完全相同,并不是同构),并输出方案。

数据范围:(1leq nleq 5 imes 10^5)

本题题解

考虑两棵树(无向)边的集合。假设有 (k) 条公共的边(在两集合里均出现过),显然 (n - 1 - k) 是操作数的一个下界。下面我们通过构造,使答案取到这个下界。

首先,这 (k) 条公共的边,我们不去动它们。那么,可以把这些边连接的节点缩起来。我们称缩好的节点为“大点”,每个大点实际是原树上的一个连通块。可以发现如下性质:对于第一棵树的每个大点,总能在第二棵树上找到唯一一个对应的大点,使得两大点对应的连通块完全相同(节点的编号和连接方式,都完全一样)。换句话说:缩点后的两棵树,节点是一一对应的

以节点 (1) 所在的大点为根,把两棵树变为有根树。定义树的叶子是没有儿子的节点(即,除非点数为 (1),任务已经完成,否则根节点不是叶子)。

现在第一棵树上全都是要拆掉的边了。我们进行如下过程:

  1. 在第一棵树上,任意选一个叶子(大点),记为 (V)。考虑 (V) 对应的连通块,考虑原第一棵树上,和该连通块里至少一个点相连的边,这些边中,现在还剩下唯一一条是还没有被缩起来的:它就是连接 (V)(V) 父亲的这条边。记这条边的两个端点分别为 (v, u),其中 (v) 来自 (V)(u) 来自 (V) 的父亲。
  2. 拆掉边 ((v, u))
  3. 因为缩点后的两棵树节点是一一对应的,所以在第二棵树上,找到点 (V) 对应的点 (V')(V') 不一定是叶子,所以它可能有很多连向外界的边。现在我们只要选择其中的一条(因为第二棵树是连通的,所以至少有一条从 (V') 出发连向外界的、还未被缩起来的边),并在第一棵树里加入这条边,就可以了。
  4. 方便起见,我们选择第二棵树上,连接 (V')(V') 父亲的边。可以证明这条边一定存在:因为 (V) 是叶子,即 (V) 不包含节点 (1),所以 (V') 也不包含节点 (1),因此 (V') 不是第二棵树的根,故存在连接 (V')(V') 父亲的边。记这条边为 ((v', u'))
  5. 在第一棵树上连接 ((v', u')),并把这条边缩起来。相当于删除了叶子 (V),并将它并入了另一个节点。

综上,我们每次会删掉一个叶子。最终整棵树一定被缩到只剩一个节点。此时任务就完成了。


考虑如何实现上述过程。我们用并查集来进行缩点。

把初始时公共的边缩完后,我们在原第一棵树上 dfs。每次对于当前节点 (u) 的儿子 (v),先递归下去。等返回时,(v) 已经变成了一个叶子。此时按上述方法处理边 ((v, u)):难点在于找出 (v')。发现它相当于,(v) 所在大点里,在第二棵树上深度最小的点。所以我们用并查集维护时,记录一下每个连通块内,在第二棵树上深度最小的点即可。

时间复杂度 (mathcal{O}(nalpha))。近似线性。

参考代码

实际提交时建议使用读入优化,详见本博客公告。

// problem: CF650E
#include <bits/stdc++.h>
using namespace std;

#define mk make_pair
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }

const int MAXN = 5e5;

int n;
int fa[2][MAXN + 5], dep[2][MAXN + 5];
vector<int> G[2][MAXN + 5];

void dfs_init(int t, int u) {
	for (int i = 0; i < SZ(G[t][u]); ++i) {
		int v = G[t][u][i];
		if (v == fa[t][u])
			continue;
		fa[t][v] = u;
		dep[t][v] = dep[t][u] + 1;
		dfs_init(t, v);
	}
}

struct DSU {
	int fa[MAXN + 5], sz[MAXN + 5];
	int val[MAXN + 5], wei[MAXN + 5]; // wei 是权重, val 取权重最大者对应的值
	
	int get_fa(int u) {
		return (u == fa[u]) ? u : (fa[u] = get_fa(fa[u]));
	}
	
	int get_val(int u) {
		return val[get_fa(u)];
	}
	void set_val(int u, int _val, int _wei) {
		u = get_fa(u);
		val[u] = _val;
		wei[u] = _wei;
	}
	void upd_val(int u, int _val, int _wei) {
		u = get_fa(u);
		val[u] = (_wei > wei[u] ? _val : val[u]);
		wei[u] = max(wei[u], _wei);
	}
	
	void unite(int u, int v, int _val, int _wei) {
		u = get_fa(u);
		v = get_fa(v);
		if (u != v) {
			if (sz[u] > sz[v])
				swap(u, v);
			fa[u] = v;
			sz[v] += sz[u];
			upd_val(v, val[u], wei[u]);
			upd_val(v, _val, _wei);
		} else {
			upd_val(v, _val, _wei);
		}
	}
	void init(int n) {
		for (int i = 1; i <= n; ++i) {
			fa[i] = i;
			sz[i] = 1;
		}
	}
	DSU() {}
} dsu;

struct Answer_t {
	int u1, v1, u2, v2;
	Answer_t() {}
	Answer_t(int _u1, int _v1, int _u2, int _v2) {
		u1 = _u1;
		v1 = _v1;
		u2 = _u2;
		v2 = _v2;
	}
};
int cnt;
Answer_t ans[MAXN + 5];

void dfs(int u) {
	for (int i = 0; i < SZ(G[0][u]); ++i) {
		int v = G[0][u][i];
		if (v == fa[0][u])
			continue;
		dfs(v);
		if (dsu.get_fa(u) != dsu.get_fa(v)) {
			int x = dsu.get_val(v); // 连通块 v 中, 深度最小的点
			ans[++cnt] = Answer_t(u, v, x, fa[1][x]);
			dsu.unite(x, fa[1][x], fa[1][x], -dep[1][fa[1][x]]);
		}
	}
}

int main() {
	cin >> n;
	for (int t = 0; t <= 1; ++t) {
		for (int i = 1; i < n; ++i) {
			int u, v;
			cin >> u >> v;
			G[t][u].push_back(v);
			G[t][v].push_back(u);
		}
		dfs_init(t, 1);
	}
	
	dsu.init(n);
	for (int i = 1; i <= n; ++i) {
		dsu.set_val(i, i, -dep[1][i]);
	}
	for (int u = 2; u <= n; ++u) {
		int v = fa[0][u];
		// 第一棵树中的无向边 (u, v)
		if (fa[1][u] == v || fa[1][v] == u) {
			// 第二棵树中也存在这条边
			dsu.unite(u, v, u, -dep[1][u]); // 缩成一个大点
			dsu.upd_val(u, v, -dep[1][v]);
		}
	}
	
	dfs(1);
	cout << cnt << endl;
	for (int i = 1; i <= cnt; ++i) {
		cout << ans[i].u1 << " " << ans[i].v1 << " " << ans[i].u2 << " " << ans[i].v2 << endl;
	}
	
	return 0;
}
原文地址:https://www.cnblogs.com/dysyn1314/p/14395610.html