2020牛客暑期多校训练营(第五场)B Graph

    根据题目描述我们可以知道,无论添加的时间顺序,添加a-b的边的大小一定是固定的,即a-b上的路径上的所有长度的异或,所以题目就可以简化成寻找完全图的最小生成树。

    设dis[n]为0-n点的链路上所有边长的的异或,则在之前所说的完全图中,a-b的边长就是dis[a] ^ dis[b],即为a-b上的路径上的所有长度的异或(假设最小公共祖先为c,dis[a]^dis[b]中有两段多余的0-c的异或会被相互中和掉)。则相当于每个点有一个值dis[n],而两点之间边长即为dia[a]^dis[b]。

    考虑优化kruskal的过程,我们找出边权最小的且边的两边没有连通的边,选择连接,方法是在trie树上贪心,首先我们对所有的点建立trie树,然后考虑怎么样连边最优,容易发现,一定是选择二进制下交最多的两个点,那么一定对应trie树上的一个前缀,所以我们只需要df树,自底向上合并即可,考虑枚举左右子树中size小的,在另一棵子树上遍历求得异或最小值,启发式合并的复杂度是 O(n∗logn) 的,加上trie树的操作,总复杂度为 O(n∗log2n)

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <vector>
using namespace std;
typedef long long ll;
const int N = 4000005;
struct edge {
	int v, len;
};
int n, rt = 0, bin[30], sz[N], ls[N], rs[N], totnode = 0, dis[110000];
vector<edge>G[110000];
ll ans = 0;

void ins(int& x, int v, int d) {
	if (!x)x = ++totnode;
	if (d == -1) { sz[x] = 1; return; }
	if (bin[d] & v)ins(rs[x], v, d - 1);
	else ins(ls[x], v, d - 1);
	sz[x] = sz[ls[x]] + sz[rs[x]];
}

int qry(int x, int v, int d) {
	if (d == -1)return 0;
	if (v & bin[d]) {
		if (rs[x])return qry(rs[x], v, d - 1);
		return qry(ls[x], v, d - 1) + bin[d];
	}
	else {
		if (ls[x])return qry(ls[x], v, d - 1);
		return qry(rs[x], v, d - 1) + bin[d];
	}
}

int merge(int x, int y, int d, int val, int sd) {
	if (d == -1)return qry(y, val, sd);
	int ret = 1 << 30;
	if (ls[x])ret = min(ret, merge(ls[x], y, d - 1, val, sd));
	if (rs[x])ret = min(ret, merge(rs[x], y, d - 1, val + bin[d], sd));
	return ret;
}

void dfs(int x, int d) {
	if (d == -1)return;
	if (ls[x])dfs(ls[x], d - 1);
	if (rs[x])dfs(rs[x], d - 1);
	if (!ls[x] || !rs[x])return;
	int l = ls[x], r = rs[x];
	if (sz[l] > sz[r])swap(l, r);
	ans += merge(l, r, d - 1, 0, d - 1) + bin[d];
}

void ini(int rt, int fa) {
	for (auto v : G[rt]) {
		if (v.v == fa)continue;
		dis[v.v] = (dis[rt] ^ v.len);
		ini(v.v, rt);
	}
}

void work()
{
	for (int i = 1; i <= n; i++)ins(rt, dis[i-1], 29);
	dfs(rt, 29);
	cout << ans << endl;
}

int main()
{
	bin[0] = 1; for (int i = 1; i <= 29; i++)
		bin[i] = bin[i - 1] << 1;
	scanf("%d", &n);
	for (int i = 1; i < n; i++) 
	{
		int a, b, c;
		scanf("%d %d %d", &a, &b, &c);
		G[a].push_back(edge{ b,c });
		G[b].push_back(edge{ a,c });
	}
	ini(0, -1);
	work();
	return 0;
}

    

猜你喜欢

转载自blog.csdn.net/chenshibo17/article/details/107579830