比赛链接:link
题意是说给定了一棵树,每条边都有一个权值,我们可以进行删边或者增边操作,每次需要保证操作后所有点是连通的,并且保证若是存在环,环上所有值异或和为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
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();
}