【題解】[CF888G] Xor-MST
阿新 • • 發佈:2022-03-05
[CF888G]Xor-MST解題報告
)ブ
在部落格園的第一篇文章!
[CF888G]Xor-MST 解題報告
目錄
題意、思路、參考資料
題意
給定有\(n\)個節點的無向完全圖,每個點有點權\(a_i\), 節點\(i\)與節點\(j\)之間的邊權為 $ a_i \oplus a_j $. 計算該圖最小生成樹的權值.
思路
處理異或:Trie
最小生成樹:\(Kruskal, Prim, Borůvka(Sollin)\)
已知:
我們會求n個數中任取兩數的最大、最小異或和,由此算是處理了\(\oplus\)有關的事;
對三種我們已知的MST演算法進行選擇:
- Kruskal
貪心,從小到大將邊加入當前選出的、用於構成MST的邊的集合 - Prim
從點出發,選擇與當前節點距離最小的(並且不在集合裡的)點加入集合,並連上那條邊。 - Borůvka (Sollin)
遍歷沒用過的邊,尋找每個連通塊的最小出邊,將這條邊兩個端點所在的兩個連通塊合併。也需要用到並查集。通俗言之,相當於將連通塊當成點來跑Kruskal,或者說,用“點”帶“連通塊”的形式跑Prim。 由於只需要 log N次合併,在處理邊權與點權有關/一些(???)的問題時會有優勢。
此題就是用Borůvka的典例。
LG @Tweetuzkidalao的這篇題解寫得很清楚,在此感謝這位神犇。
整理一下,我們找到了思路:Borůvka + 01 Trie!o( ̄▽ ̄
但是在程式碼實現中,一個小問題出現了。
程式碼實現
怎麼找一個連通塊的最小出邊呢?
由常識可知,當兩個數在01 Trie裡的LCA最深時,這兩個數的異或值最小。
證明:\(2^a>2^{a-1}+2^{a-2}+\cdots+2^1+2^0\),顯然左式=右式+1.
所以權值在01 Trie上LCA最深的兩個點必然先連通。因此,採用分治思想,字典樹上當前節點的0兒子和1兒子聯通的代價為 在0兒子裡的點的MST權值+1兒子裡的點的MST權值+0塊和1塊之間最小邊的邊權。最小邊的邊權即為在0兒子和1兒子裡分別找一個點,將這兩個點的權異或得到的最小值。
AC Code
#include<bits/stdc++.h> using namespace std; #define ll long long const int N=2e5+5; const ll INF=2147483647; vector<int> a; int trie[N<<5][2],cnt; int n; void ins(int x){ int u=0; for(int i=31;i>=0;i--){ if(!trie[u][x>>i &1]) trie[u][x>>i &1]=++cnt; u=trie[u][x>>i &1]; } } ll xr(int t,int f,int r){ if(t==-1) return 0; int u=r; ll ret=0; for(int i=t;i>=0;i--){ if(trie[u][f>>i &1]){ u=trie[u][f>>i &1]; } else { u=trie[u][!(f>>i &1)]; ret+=1<<i; } } return ret; } ll b(const vector<int> &now,int t,int r){//boruvka,t為當前遞迴到的層數 if(t<0 || !r) return 0; int l=now.size(); vector<int>ch[2];// ch向量用於存當前分治樹的0兒子和1兒子下的點的權值 for(int i=0;i<l;i++){ ch[now[i]>>t &1].push_back(now[i]); } if(!ch[0].size()) return b(ch[1],t-1,trie[r][1]); if(!ch[1].size()) return b(ch[0],t-1,trie[r][0]); ll minn=INF; int siz=ch[0].size(); for(int i=0;i<siz;i++){ minn=min(minn,(1<<t)*1LL+xr(t-1,ch[0][i],trie[r][1])); //取1兒子裡的點與0兒子裡的點分別異或,取最小值作為當前兩個連通塊聯通的代價 } return minn+b(ch[0],t-1,trie[r][0])+b(ch[1],t-1,trie[r][1]); } int main(){ ins(0); scanf("%d",&n); for(int i=0,j;i<n;i++){ scanf("%d",&j); a.push_back(j); ins(j); } printf("%lld",b(a,30,1)); return 0; }
一點叭叭:花了兩天晚上。第一天晚上特意找最小生成樹的模板題打了Borůvka,第二天晚上花了兩個半小時看題解和寫程式碼和看題解。實際耗時遠不及此。
參考資料
- 洛谷 @Tweetuzkidalao的這篇題解
- OI WIKI
\(\color{Teal}{BONA (●◡●)}\)