noip2019集訓測試賽(二十一)Problem B: 紅藍樹
Description
有一棵
N個點,頂點標號為1到N的樹。N−1條邊中的第i條邊連接頂點ai和bi。
每條邊在初始時被染成藍色。高橋君將進行N−1次操作,來把這棵藍色的樹變成紅色的樹。
* 選一條僅包含藍色邊的簡單路徑,並刪除這些邊中的一條。
* 然后在路徑的兩個端點中間連一條紅色的邊。
他的目標是,對於每一個i,都有一條紅色的邊連接ci和di。
現在請你判斷是否可能達成他的目標。
每條邊在初始時被染成藍色。高橋君將進行N−1次操作,來把這棵藍色的樹變成紅色的樹。
* 選一條僅包含藍色邊的簡單路徑,並刪除這些邊中的一條。
* 然后在路徑的兩個端點中間連一條紅色的邊。
他的目標是,對於每一個i,都有一條紅色的邊連接ci和di。
現在請你判斷是否可能達成他的目標。
Input
題目數據按一下格式從標准輸入輸出輸入:
NN
a1 b1
⋮
aN−1 bN−1
c1 d1
⋮
cN−1 dN−1
NN
a1 b1
⋮
aN−1 bN−1
c1 d1
⋮
cN−1 dN−1
Output
如果目標可以達成,輸出`YES`,否則輸出`NO`。
Sample Input
sample input 1:
3
1 2
2 3
1 3
3 2
sample input 2:
5
1 2
2 3
3 4
4 5
3 4
2 4
1 4
1 5
sample input 3:
6
1 2
3 5
4 6
1 6
5 1
5 3
1 4
2 6
4 3
5 6
Sample Output
sample output 1:
YES
sample output 2:
YES
sample output 3:
NO
HINT
樣例 1 :
目標可以達成:* 首先,選擇連接頂點 1和33的邊,並移除1到2之間的藍色邊,並在1和3之間生成一條紅色邊;
* 然后,選擇連接頂點2和3的邊,並刪去2和3之間的藍色邊,並在2和3之間生成一條紅色邊。
* 2≤N≤105
* 1≤ai,bi,ci,di≤N
* ai≠bi
* ci≠di
* 輸入的兩個圖都是樹。
解析:
如果可以做到將紅樹變為藍樹,那么在改變完N-2條邊后,最后的沒有連上一條藍邊必與最后一條紅邊重合
那么這一條重合的邊可以在以前所有的連邊中任意使用
那么我們可以對於原樹中每一條既是紅的又是藍的邊的兩個端點縮成一個點(刪去原本連接他們的邊,再把其中一個點的所有邊轉到另一個點上,用啟發式合並來縮點)
如果最終整棵樹能夠縮成一個點,那么就是YES,否則就是NO
啟發式合並的總耗時是O(nlogn)
所以整個算法的時間復雜度是O(nlogn)
#include<iostream> #include<cstdio> #include<set> using namespace std; struct data{ int x,y; }t[200001]; int n,m,f[200001],x,y,cnt; set<int> v[200001]; int fa(int a){ if(f[a]!=a)f[a]=fa(f[a]); return f[a]; } void ins(int a,int b){ set<int>::iterator id1=v[a].find(b),id2=v[b].find(a); if(id1==v[a].end()){ v[a].insert(b); v[b].insert(a); }else{ cnt++; v[a].erase(b); v[b].erase(a); t[cnt].x=a; t[cnt].y=b; } } int main(){ scanf("%d",&n); for(int i=1;i<=n*2-2;i++){ scanf("%d%d",&x,&y); ins(x,y); } for(int i=1;i<=n;i++)f[i]=i; while(cnt){ x=fa(t[cnt].x); y=fa(t[cnt].y); cnt--; if(v[x].size()>v[y].size())swap(x,y); f[x]=y; for(int i:v[x]){ v[i].erase(x); ins(i,y); } v[x].clear(); m++; } if(m==n-1)printf("YES\n"); else printf("NO\n"); }