原題
題目大意
給你兩棵樹,你要讓原樹變爲新樹,每次可以刪一條邊加一條邊,但是在任意時刻,不能出現環,問最小步數以及刪邊加邊的方案。
解題思路
首先考慮那些在初態和終態下都出現的邊。這些邊顯然都是不動的,把它們連接的兩個點合併起來。合併時要維護這個集合內的點與集合外的點的連邊。
答案很顯然爲總邊數減去不動的邊數,即每次刪一條原樹中的邊,並增加一條終態的樹上的邊。
按照任意順序刪去原樹中要改變的邊
參考代碼
#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;
}