比赛链接:link
B Boruvka算法&异或字典树
题意是说给定了一棵树,每条边都有一个权值,我们可以进行删边或者增边操作,每次需要保证操作后所有点是连通的,并且保证若是存在环,环上所有值异或和为0。求最小权值和。
由异或的性质可知,从某个顶点
i
i
i 到顶点
j
j
j 若是有连边,这个连边的值是确定的;若是我们确定了某一个点的值,其他点的值也可以确定下来,路径异或就可以转成顶点异或。比如我们假定
1
1
1 号结点的值为
0
0
0,这样其他结点的值也可以被确定,两点间连边的值就等于两点的异或值。
那么问题就转化成
n
(
n
≤
1
e
5
)
n (n ≤1e5)
n(n≤1e5) 个结点,每个结点都有权值,两点间的连边值为结点的异或,求最小生成树的代价。kruskal 算法的复杂度为
O
(
n
2
l
o
g
n
)
O(n^2logn)
O(n2logn), 肯定不行,这时候就需要用到一种神奇的 Boruvka 算法来解决。
Boruvka算法的核心思想是:从所有当前的连通块向其他连通块扩展出最小边,直到只剩一个连通块。每次循环找到当前连通块伸出去扩展的最小边,然后连接这些最小边,由于每次连通块至少减半,所以最多进行
l
o
g
n
logn
logn 次,那么时间复杂度就由每次循环的时间决定了。
而我们做这题其实不需要具体实现 Boruvka 算法,而是需要它的思想。当某个连通块和另一个连通块进行连接时,若异或不为0,记二进制为
1
1
1 的最高位为
i
i
i, 那么这两个连通块,肯定一个块里第
i
i
i 位的点权全为
1
1
1,一个全为
0
0
0。
这样的话我们就可以用神奇的异或字典树处理,每一个数字都可以用二进制表示塞进这棵树中,每次合并的集合都是最高位的1不同的两个集合进行合并,于是可以从上往下做,从最高位把集合分开,然后查询两个集合的最小连边。处理时有一个小技巧,可以将所有权值先排序,这样个集合内的元素就在一个连续的区间里。
我觉得洛谷这几个题解写的更好 link, 其实是CF888G的原题,复杂度为
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O(nlog2n)。
这个异或字典树太神了
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> P;
const int maxn = 1e5 + 10;
const int INF = 0x3f3f3f3f;
const ll mod = 1e9 + 7;
int trie[maxn*30][2], L[maxn*30], R[maxn*30], a[maxn];
int root, n, tol;
void Insert(int &now, int i, int dep) //给a[i]个数插入Trie树,现在是第dep位
{
if(!now) now = ++tol;
L[now] = min(L[now], i); R[now] = max(R[now], i); //统计这一段有哪些数
if(dep <= 0) return; //到了第0位,不需要往下了
int bit = a[i]>>(dep-1) & 1;
Insert(trie[now][bit], i, dep - 1);
}
ll query(int now, int val, int dep) //从now结点开始,此时是第dep位
{
if(dep <= 0) return 0;
int bit = val>>(dep-1) & 1;
if(trie[now][bit]) return query(trie[now][bit], val, dep - 1);
return query(trie[now][bit^1], val, dep - 1) + (1<<(dep-1));
}
ll dfs(int now, int dep) //当前到第dep位,将其左右兄弟的连通块合并
{
if(dep <= 0) return 0;
if(trie[now][0] && trie[now][1]) //如果左右两边都有连通块
{
ll mx = 2e15; //mx为合并左右两个连通块的花费
//if(R[trie[now][0]] - L[trie[now][0]] <= R[trie[now][1]] - L[trie[now][1]]) //启发式搜索
for(int i = L[trie[now][0]]; i <= R[trie[now][0]]; i++)
mx = min(mx, query(trie[now][1], a[i], dep - 1));
// else
// for(int i = L[trie[now][1]]; i <= R[trie[now][1]]; i++)
// mx = min(mx, query(trie[now][0], a[i], dep - 1));
return dfs(trie[now][0], dep - 1) + dfs(trie[now][1], dep - 1) + mx + (1<<(dep-1));
}
if(trie[now][0]) return dfs(trie[now][0], dep - 1);
return dfs(trie[now][1], dep - 1);
}
void xor_mst()
{
sort(a + 1, a + 1 + n);
memset(L, INF, sizeof(L));
for(int i = 1; i <= n; i++) Insert(root, i, 30);
printf("%lld\n",dfs(root, 30));
}
vector<P> G[maxn];
int cnt;
void Dfs(int x, int fa, int val)
{
a[++cnt] = val;
for(auto &y: G[x])
{
if(y.first == fa) continue;
Dfs(y.first, x, val ^ y.second);
}
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n - 1; i++)
{
int x, y, z;
scanf("%d %d %d", &x, &y, &z);
G[x].push_back(P(y, z)); G[y].push_back(P(x, z));
}
Dfs(0, -1, 0);
xor_mst();
}