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

本文介绍了一种利用异或运算性质解决完全图最小生成树问题的方法。通过构建字典树并采用贪心策略,避免不必要的花费,达到时间复杂度O(nlogn)。关键步骤包括DFS遍历高位节点,合并连通块并优化子树结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

给你一张完全图,任意两个点之间的边权是ai⊕aja_i\oplus a_jaiaj.问你最小生成树大小

题目思路

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

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

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

时间复杂度:O(nlogn)O(nlogn)O(nlogn)

解释:
最差情况就是 字典树是一颗完全二叉树.这个时候需要枚举n个节点。
递归的复杂度是节点子树大小.
O(∑sz(i))=O(∑dep(i))=O(∑i=0logn2i∗i)=O(nlogn)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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值