1. 程式人生 > 其它 >【題解】[CF888G] Xor-MST

【題解】[CF888G] Xor-MST

[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,第二天晚上花了兩個半小時看題解和寫程式碼和看題解。實際耗時遠不及此。

參考資料

\(\color{Teal}{BONA (●◡●)}\)