2020牛客暑假多校第五场补题

比赛链接: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(n1e5) 个结点,每个结点都有权值,两点间的连边值为结点的异或,求最小生成树的代价。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();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值