CF888G-巧妙字典树+暴力分治(异或最小生成树)

题目大意

给你一张完全图,任意两个点之间的边权是 a i ⊕ a j a_i\oplus a_j aiaj.问你最小生成树大小

题目思路

看到异或位运算。自然想到字典树.将所有点插入到字典树.看看效果
在这里插入图片描述
性质:
S i S_i Si为节点 i i i的所有叶子节点在图中所构成的连通块.

1.图中任意两点连边,等价于树上的对应叶子节点 l c a lca lca往下的花费。所以 L C A LCA LCA越深越好
2.任意一个节点,若存在两个儿子 a , b a,b a,b,那么对于将 S a , S b S_a,S_b Sa,Sb联通时,这一位的花费是固定不可避免的.
反之,若只存在一个儿子,连通 S a S_a Sa 本身,这一位的花费是没有的。

所以容易设计出暴力+贪心的方法:
dfs从高位开始遍历所有 含有两个儿子 a , b a,b a,b的节点,然后递归的在 a , b a,b a,b子树里贪心的尽量移动同样的位值。使得花费最小。

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

解释:
最差情况就是 字典树是一颗完全二叉树.这个时候需要枚举n个节点。
递归的复杂度是节点子树大小.
O ( ∑ s z ( i ) ) = O ( ∑ d e p ( i ) ) = O ( ∑ i = 0 l o g n 2 i ∗ i ) = O ( n l o g n ) O(\sum sz(i))=O(\sum dep(i))=O(\sum_{i=0}^{logn}2^i*i)=O(nlogn) O(sz(i))=O(dep(i))=O(i=0logn2ii)=O(nlogn)

算法正确性:
根据Brouka算法,
最开始叶子节点合并时,只有当他们不在同一个点时,会产生花费。
第一轮合并连通块后。在字典树上缩点。递归论证.

不难看出上述算法就是Brouka算法的过程。只是逆过来了更好处理。
当然也可以直接模拟B算法,但是需要支持动态开点+合并操作的字典树。复杂度也会比较高.

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vll vector<ll>
#define fi first
#define se second
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
const int up = 30;
int a[maxn];
namespace Trie{
    int tr[maxn * 20][2] , cnt;
    void add (int x){
        int u = 0;
        for (int i = up ; i >= 0 ; i--){
            int v = (x >> i) & 1;
            if (!tr[u][v]) tr[u][v] = ++cnt;
            u = tr[u][v];
        }
    }
    int ask (int u , int v , int step){
        if (step == -1) return 0;
        int x = -1 , y = -1;
        if (tr[u][0] && tr[v][0]) x = ask(tr[u][0],tr[v][0],step-1);
        if (tr[u][1] && tr[v][1]) y = ask(tr[u][1],tr[v][1],step-1);
        if (~x && ~y) return min(x , y);
        if (~x) return x; if (~y) return y;
        x = y = -1;
        if (tr[u][0] && tr[v][1]) x = ask(tr[u][0],tr[v][1],step-1) + (1 << step);
        if (tr[u][1] && tr[v][0]) y = ask(tr[u][1],tr[v][0],step-1) + (1 << step);
        if (~x && ~y) return min(x , y);
        if (~x) return x;
        return y;
    }
    ll dfs (int u , int step){
        if (step == -1) return 0;
        ll ans = 0;
        if (tr[u][0] && tr[u][1]){
            ans += 1ll * ask(tr[u][0] , tr[u][1] , step - 1) + (1ll << step);
        }
        if (tr[u][0]) ans += dfs(tr[u][0] , step - 1);
        if (tr[u][1]) ans += dfs(tr[u][1] , step - 1);
        return ans;
    }
}

int main()
{
    ios::sync_with_stdio(false);
    int n; cin >> n;
    for (int i = 1 ; i <= n ; i++) {
        int x;cin >> x;
        Trie::add(x);
    }
    cout << Trie::dfs(0 , up) << endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值