1. 程式人生 > 實用技巧 >題解 CF888G 【Xor-MST】

題解 CF888G 【Xor-MST】

這是作者對於這道典型例題思路的記錄,用於作者自己本人對於字典樹和異或的應用的加深印象,同時也歡迎大家的閱讀。

這道題據說是有合理的最小生成樹演算法可以過(好像是\(Boruvka\)),但是沒有試過,感覺是不可以過的。

這一道題的\(n<=2e5\),用普通的\(Prim\)\(Kruskal\)絕對是會炸的,無論是時間還是空間,所以我們可以嘗試從異或這一運算入手。

因為異或這一運算是相同的為\(0\),不同的為\(1\),所以我們可以嘗試找出每個數字的最長字首(算上前導\(0\)),說不定可以用字尾陣列還是字尾自動機求\(lcp\)???

但是這裡可以使用字典樹,即建一棵有\(n\)個葉子節點的樹使得每一條由根節點到葉子節點的鏈都代表著一個數字。然後我們再利用這棵優秀的二叉樹(因為只有\(0\)

\(1\))來進行查詢操作。

void add(int x)
{
	int s[35];
	for(int i=30;i>=1;--i)
	{
		s[i]=x&1;
		x>>=1;
	}
	int tmp=0;
	for(int i=1;i<=30;++i)
	{
		if(!tr[tmp].son[s[i]])
		tr[tmp].son[s[i]]=++size;
		tmp=tr[tmp].son[s[i]];
	}
	return ;
}

以上是建樹部分的程式碼。就是建一棵很普通的字典樹,記得補上前導\(0\)

查詢操作則儘量走相同的方向,因為我們是從高位走向低位的,所以我們要在儘量高的位置保持異或值為\(0\)

,實在不行時就走不同的路,再更新答案。

ll find(int u,int v,int dep)
{
	if(!tr[u].son[0]&&!tr[u].son[1]&&!tr[v].son[0]&&!tr[v].son[1])
	return 0;
	ll res=2e9+7;
	if(tr[u].son[0]&&tr[v].son[0])
	res=min(res,find(tr[u].son[0],tr[v].son[0],dep-1));
	if(tr[u].son[1]&&tr[v].son[1])
	res=min(res,find(tr[u].son[1],tr[v].son[1],dep-1));
	if(res<2e9+7)
	return res;
	if(tr[u].son[0]&&tr[v].son[1])
	res=min(res,(1<<(dep-1))+find(tr[u].son[0],tr[v].son[1],dep-1));
	if(tr[u].son[1]&&tr[v].son[0])
	res=min(res,(1<<(dep-1))+find(tr[u].son[1],tr[v].son[0],dep-1));
	return res;
}
ll ans=0;
void dfs(int u,int dep)
{
	if(tr[u].son[0]&&tr[u].son[1])
	ans+=(1<<(dep-1))+find(tr[u].son[0],tr[u].son[1],dep-1);
	if(tr[u].son[0])
	dfs(tr[u].son[0],dep-1);
	if(tr[u].son[1])
	dfs(tr[u].son[1],dep-1);
}

以上部分為查詢操作,其中這棵二叉樹只有\(n\)個葉子節點,所以在沒有數字重複的情況下,肯定只有\(n-1\)個節點的兒子數為\(2\),所以直接\(dfs\)即可。

以下為完整程式碼:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=2e5+5;
int n;
int a[N],loc[N];
struct Node
{
	int son[2];
}tr[N*30];
int size=0;
void add(int x)
{
	int s[35];
	for(int i=30;i>=1;--i)
	{
		s[i]=x&1;
		x>>=1;
	}
	int tmp=0;
	for(int i=1;i<=30;++i)
	{
		if(!tr[tmp].son[s[i]])
		tr[tmp].son[s[i]]=++size;
		tmp=tr[tmp].son[s[i]];
	}
	return ;
}
ll find(int u,int v,int dep)
{
	if(!tr[u].son[0]&&!tr[u].son[1]&&!tr[v].son[0]&&!tr[v].son[1])
	return 0;
	ll res=2e9+7;
	if(tr[u].son[0]&&tr[v].son[0])
	res=min(res,find(tr[u].son[0],tr[v].son[0],dep-1));
	if(tr[u].son[1]&&tr[v].son[1])
	res=min(res,find(tr[u].son[1],tr[v].son[1],dep-1));
	if(res<2e9+7)
	return res;
	if(tr[u].son[0]&&tr[v].son[1])
	res=min(res,(1<<(dep-1))+find(tr[u].son[0],tr[v].son[1],dep-1));
	if(tr[u].son[1]&&tr[v].son[0])
	res=min(res,(1<<(dep-1))+find(tr[u].son[1],tr[v].son[0],dep-1));
	return res;
}
ll ans=0;
void dfs(int u,int dep)
{
	if(tr[u].son[0]&&tr[u].son[1])
	ans+=(1<<(dep-1))+find(tr[u].son[0],tr[u].son[1],dep-1);
	if(tr[u].son[0])
	dfs(tr[u].son[0],dep-1);
	if(tr[u].son[1])
	dfs(tr[u].son[1],dep-1);
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;++i)
	scanf("%d",&a[i]);
	sort(a+1,a+1+n);
	n=unique(a+1,a+1+n)-a-1;
	for(int i=1;i<=n;++i)
	add(a[i]);
	dfs(0,30);
	printf("%lld\n",ans);
	return 0;
}