原题
题目大意
给你两棵树,你要让原树变为新树,每次可以删一条边加一条边,但是在任意时刻,不能出现环,问最小步数以及删边加边的方案。
解题思路
首先考虑那些在初态和终态下都出现的边。这些边显然都是不动的,把它们连接的两个点合并起来。合并时要维护这个集合内的点与集合外的点的连边。
答案很显然为总边数减去不动的边数,即每次删一条原树中的边,并增加一条终态的树上的边。
按照任意顺序删去原树中要改变的边
(u,v)
,然后连一条从
u
所在集合中的点到
参考代码
#include <map>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int maxn=500010;
struct Edge {int x,y;}Ed[maxn];
map <LL,int> M;
int head[maxn],next[maxn<<1],E[maxn<<1],Ecnt;
int Ehead[maxn],Enext[maxn<<1],Etail[maxn<<1],EE[maxn<<1],EEcnt;
int fa[maxn];
int n,m;
void Add_Edge(int x,int y) {
next[++Ecnt]=head[x];
head[x]=Ecnt;
E[Ecnt]=y;
next[++Ecnt]=head[y];
head[y]=Ecnt;
E[Ecnt]=x;
}
void Add_EdgeE(int x,int y,int p) {
Enext[++EEcnt]=Ehead[x];
if (Ehead[x]==0) Etail[x]=EEcnt;
Ehead[x]=EEcnt;
EE[EEcnt]=p;
Enext[++EEcnt]=Ehead[y];
if (Ehead[y]==0) Etail[y]=EEcnt;
Ehead[y]=EEcnt;
EE[EEcnt]=p;
}
int Find(int x) {return fa[x]==x?x:fa[x]=Find(fa[x]);}
void DFS(int x,int father)
{
for (int i=head[x];i;i=next[i]) if (E[i]!=father) DFS(E[i],x);
if (x==1) return;
if (Find(x)==Find(father)) return;
int now=Ehead[Find(x)];
while (Ehead[Find(x)] && Find(Ed[EE[now]].x)==Find(Ed[EE[now]].y))
now=Ehead[Find(x)]=Enext[Ehead[Find(x)]];
if (!now) return;
int u=Ed[EE[now]].x,v=Ed[EE[now]].y;
printf("%d %d %d %d\n",father,x,u,v);
Enext[Etail[Find(u)]]=Ehead[Find(v)];
Etail[Find(u)]=Etail[Find(v)];
fa[Find(v)]=Find(u);
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++) fa[i]=i;
for (int i=1,x,y;i<n;i++)
{
scanf("%d%d",&x,&y);
if (x>y) swap(x,y);
M[(LL)x*n+y]=1;
Add_Edge(x,y);
}
for (int i=1,x,y;i<n;i++)
{
scanf("%d%d",&x,&y);
if (x>y) swap(x,y);
if (M[(LL)x*n+y]==1) {fa[Find(x)]=Find(y);continue;}
Ed[++m].x=x,Ed[m].y=y;
}
for (int i=1;i<=m;i++) Add_EdgeE(Find(Ed[i].x),Find(Ed[i].y),i);
printf("%d\n",m);
DFS(1,0);
return 0;
}