题意:有两棵树,现在可以将第一棵树的边去除,然后在两个节点之间连一条新边使得第一棵树变成第二棵树,但是这种操作有一个限制条件,就是任何时刻,图中不能出现环,问操作数最少的步骤。
思路:首先可以明确一点,就是一定存在某种操作序列,使得第一棵树变成第二棵树。考虑两棵树之间相同的边,可以把这些边包含的点缩成一个点考虑,要在这两个缩点后的图上进行操作,只需要每次从树的叶子节点开始改边就能保证满足题目中没有环的条件。
我们称这缩点后的点为大点,可以发现第一棵树除了根大点外每个大点中深度最低的那个小点与父节点之间的边是要被改变的,而他们要改成的边是第二棵树中这个大点中深度最低的点与父节点之间的边,所以我们考虑用并查集来做,每个大点即一个并查集,并查集的根为第二棵树中要改变的点,然后在第一棵树中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;
}