• 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;
    }
    
  • 相关阅读:
    2019版:第七章:(1)Redis 持久化—RDB
    第三章:(2)高可用 之 集群下dubbo负载均衡配置
    2019版:第六章:(1)Redis 事务
    2019版:第七章:(3)Redis 持久化—RDB和AOF
    第二章:(7)Dubbo 配置与SpringBoot整合
    第二章:(6)Dubbo 常用配置之 本地存根
    2019版:第六章:(2)Redis 事务—秒杀案例
    2019版:第七章:(2)Redis 持久化—AOF
    敏捷开发修炼之道读书笔记2
    敏捷开发修炼之道读书笔记1
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/14395610.html
Copyright © 2020-2023  润新知