CodeForces - 888G. Xor-MST

题意:

给定一个有 n 个点的完全图,两点边权为点权异或结果,求最小生成树。(n <= 2e5)

链接:

https://vjudge.net/problem/CodeForces-888G

解题思路:

由于是完全图,不能将边权都求出来,无法使用 Kruskal 和 Prim。这里结合另一种最小生成树算法的思想,Boruvka算法,每轮选择连接联通块的最小边,由于是异或,即对一个联通块建立字典树,另一个联通块内的点求其最小异或和。考虑贪心,根据 bit 位分组,先根据最高位为 0、1 分两组,之间选择一条最小边连接,而两边是子问题,递归求解即可。

参考代码:

#include

using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define pb push_back
#define sz(a) ((int)a.size())
#define mem(a, b) memset(a, b, sizeof a)
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 2e5 + 5;
const int maxm = 6e6 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

int nxt[maxm][2];
int a[maxn];
int n, cnt;

int add(){

	mem(nxt[++cnt], 0); return cnt;
}

void init(){

	cnt = -1; add();
}

void insert(int x){

	int p = 0;
	for(int i = 29; i >= 0; --i){

		int t = (x >> i) & 1;
		if(!nxt[p][t]) nxt[p][t] = add();
		p = nxt[p][t];
	}
}

int query(int x){

	int p = 0, ret = 0;
	for(int i = 29; i >= 0; --i){

		int t = (x >> i) & 1;
		if(nxt[p][t]) p = nxt[p][t];
		else p = nxt[p][t ^ 1], ret |= 1 << i;
	}
	return ret;
}

ll dfs(int l, int r, int d){

	if(d < 0 || l >= r) return 0;
	int mid = 0;
	for(int i = l; i <= r; ++i){

		if((a[i] >> d) & 1) break;
		mid = i;
	}
	if(!mid || mid == r) return dfs(l, r, d - 1);
	int tmp = 1 << 30; init();
	for(int i = l; i <= mid; ++i) insert(a[i]);
	for(int i = mid + 1; i <= r; ++i) tmp = min(tmp, query(a[i]));
	return tmp + dfs(l, mid, d - 1) + dfs(mid + 1, r, d - 1);
}

int main(){

//	ios::sync_with_stdio(0); cin.tie(0);
	cout << inf << endl;
	cout << (1 << 30) << endl;
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	sort(a + 1, a + 1 + n);
	ll ret = dfs(1, n, 29);
	printf("%lld\n", ret);
	return 0;
}

你可能感兴趣的:(图论,#,生成树)