給定一棵\(n\)個節點的藍邊樹,再給定一棵\(n\)個節點的紅邊樹。請經過若干次操做將藍樹變成紅樹。操做要求和過程以下:
1.選定一條邊全爲藍色的路徑;
2.將路徑上的一條藍邊斷開,並將路徑的兩個端點之間連一條紅邊。
問可否實現目標。
spa
咱們發現這個過程只會作剛好\(n-1\)次,由於每次都會減小一條藍邊、增長一條紅邊。
考慮紅樹上的一條邊\((u,v)\),顯然在藍樹上操做時,咱們選擇了一條以\(u\)和\(v\)爲端點的路徑,才造就了這條邊。所以紅樹上的每條邊和藍樹上的每次操做一一對應。
咱們至關於要以某種順序執行這些操做:對於操做\((u,v)\),保證操做前\(u\)和\(v\)連通,而後咱們斷開\(u\)到\(v\)路徑上的一條邊。問全部操做可否執行完。
若是咱們要執行一個操做A,那麼對於當下該路徑上的全部邊,咱們只能斷開只有A佔用的邊。形式化地講,若是對於每個未執行操做,都將路徑上的邊+1;那麼當前咱們只能斷開邊權爲1的邊。
咱們想用樹剖的方式維護並模擬這個操做,但這很是難實現。由於尋找邊權爲1的邊這個操做,或許還須要樹套樹來維護,很是麻煩。
正難則反,考慮整個過程反向進行:
對於最後一次操做,樹上必定只剩下一條邊\((u,v)\),且最後一次操做也是\((u,v)\)。考慮倒數第二次操做,它要麼是\((u,z)\),\(z\)是從最後一次操做的\((u,v)\)這條邊延伸出的另外一條邊\((v,z)\),要麼是另成的一條獨立的邊\((x,y)\).....
手玩一會,咱們發現:若是把兩棵樹建在一塊兒,那麼每次可操做的邊,就是兩棵樹中都存在的邊。操做完以後,咱們把操做邊的兩個端點縮點,繼續重複上述操做。若是操做可以執行剛好\(n-1\)次,則有解,不然無解。
接下來關鍵是怎麼實現。咱們根據想的方法,將兩棵樹實實在在地建在一塊兒。用\(n\)個set維護每一個點的出邊到達點,用一個map維護兩兩點之間邊的數量。若是某兩個點之間的連邊數量等於2,則確定這條邊在兩棵樹中間都出席那了,咱們將這兩個點組成的邊塞進隊列裏,表示這個隊列裏的邊在當前均可以操做。接下來,咱們不斷從隊頭拿出邊,進行縮點操做:啓發式合併set,刪邊或加邊直接操做set和map就好。
時間複雜度\(\mathcal O(n \log^2 n)\)
code
#include <cstdio> #include <set> #include <queue> #include <map> #define mp make_pair using namespace std; typedef long long ll; typedef pair<int,int> pii; typedef set<int> si; typedef set<int>::iterator sit; const int N=100005; int n; int bl[N]; si s[N]; queue<pii> q; map<ll,int> g; inline void swap(int &x,int &y){ x^=y^=x^=y; } int find(int u){ return bl[u]==u?u:(bl[u]=find(bl[u])); } inline ll getID(int x,int y){ if(x>y) swap(x,y); return 1ll*n*y+x; } void addEdge(int u,int v){ s[u].insert(v); s[v].insert(u); ll eid=getID(u,v); int t=g[eid]+1; g[eid]++; if(t==2) q.push(mp(u,v)); } void removeEdge(int u,int v){ s[u].erase(v); s[v].erase(u); g.erase(getID(u,v)); } void readData(){ scanf("%d",&n); int u,v; for(int i=1;i<=(n-1)<<1;i++){ scanf("%d%d",&u,&v); addEdge(u,v); } } bool solve(){ for(int i=1;i<=n;i++) bl[i]=i; for(int i=1;i<n;i++){ int u,v; if(q.empty()) return false; u=q.front().first; v=q.front().second; q.pop(); u=find(u); v=find(v); if(s[u].size()>s[v].size()) swap(u,v); bl[u]=v; removeEdge(u,v); for(sit i=s[u].begin(),j;i!=s[u].end();i=j){ j=i; j++; int x=*i; x=find(x); removeEdge(u,x); addEdge(v,x); } } return true; } int main(){ readData(); puts(solve()?"YES":"NO"); return 0; }