Codeforces Round 650E Clockwork Bomb(并查集)

题意:有两棵树,现在可以将第一棵树的边去除,然后在两个节点之间连一条新边使得第一棵树变成第二棵树,但是这种操作有一个限制条件,就是任何时刻,图中不能出现环,问操作数最少的步骤。
思路:首先可以明确一点,就是一定存在某种操作序列,使得第一棵树变成第二棵树。考虑两棵树之间相同的边,可以把这些边包含的点缩成一个点考虑,要在这两个缩点后的图上进行操作,只需要每次从树的叶子节点开始改边就能保证满足题目中没有环的条件。
我们称这缩点后的点为大点,可以发现第一棵树除了根大点外每个大点中深度最低的那个小点与父节点之间的边是要被改变的,而他们要改成的边是第二棵树中这个大点中深度最低的点与父节点之间的边,所以我们考虑用并查集来做,每个大点即一个并查集,并查集的根为第二棵树中要改变的点,然后在第一棵树中dfs一次,每次如果遇见不在第二棵树中的边,那么查询当前节点所在并查集中的根,将第一棵树中这个节点和父节点之间的边改成他所在并查集的根与它在第二棵树中父节点之间的边。注意dfs的时候先处理子节点再处理当前节点,因为这样可以保证先从叶子节点开始处理,不会在操作过程中出现环。
#include<bits/stdc++.h>
#define eps 1e-6
#define LL long long
#define pii pair<int, int>
#define pb push_back
#define mp make_pair
//#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int MAXN = 5000100;
//const int INF = 0x3f3f3f3f;
struct Ans {
	int a, b, c, d;
	Ans(int a, int b, int c, int d) : a(a), b(b), c(c), d(d) {}
};
int n;
vector<Ans> ans;
vector<int> G[2][MAXN];
int fa[2][MAXN];
int pa[MAXN];

int Find(int x) 
{
	return x == pa[x] ? x : Find(pa[x]);
}


void dfs(int id, int cur, int p) {
	for (int i = 0; i < G[id][cur].size(); i++) {
		int u = G[id][cur][i];
		if (u == p) continue;
		fa[id][u] = cur;
		dfs(id, u, cur);
	}
}


void dfs2(int cur, int p) {
	for (int i = 0; i < G[0][cur].size(); i++) {
		int u = G[0][cur][i];
		if (u == p) continue;
		dfs2(u, cur);
		if (cur != fa[1][u] && u != fa[1][cur]) {
			ans.pb(Ans(u, cur, Find(u), fa[1][Find(u)]));
		}
	}
}
int main()
{
    //freopen("input.txt", "r", stdin);
	scanf("%d", &n);
	for (int i = 1; i < n; i++) {
		int u, v;
		scanf("%d%d", &u, &v);
		G[0][u].pb(v);
		G[0][v].pb(u);
	}
	for (int i = 1; i < n; i++) {
		int u, v;
		scanf("%d%d", &u, &v);
		G[1][u].pb(v);
		G[1][v].pb(u);
	}
	dfs(0, 1, 0);
	dfs(1, 1, 0);
	for (int i = 2; i <= n; i++) {
		int u = fa[1][i];
		if (u == fa[0][i] || i == fa[0][u]) 
			pa[i] = u;
		else
			pa[i] = i;
	}
	dfs2(1, 0);
	cout << ans.size() << endl;
	for (int i = 0; i < ans.size(); i++) 
		printf("%d %d %d %d\n", ans[i].a, ans[i].b, ans[i].c, ans[i].d);
    return 0;
}
















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值