zoukankan      html  css  js  c++  java
  • Graph【XOR-MST转换]

    题意

    给一棵有 (n) 个节点的树,第 (i) 条边有边权 (W_i)

    我们可以无限次地对其增添或删除边,但要求全程始终保证:

    • 图始终是联通的
    • 任意把一个环(如果存在)上边的权值的异或和为 (0)

    最后得到一颗新树(也可保持原树不变),使得边权之和最少,求边权之和的最小值。

    (2leq N leq 10^5,0leq W_i leq 2^{30})

    题目链接:https://ac.nowcoder.com/acm/contest/5670/B

    分析

    如果任意两个点之间连边,那么该边的权值确定,因为要保证环上的边权值异或值为 (0)

    转换

    异或最小生成树问题,是在只给定各点点权值、且各边边权为两端点点权的异或值的完全图上,求最小生成树。本题告知了各边的边权,但是当指定某一点的点权值时,可以通过与边权异或得到其它点的点权值(边权转点权)。对于树上不连通的两点 (u,v),设原树上的路径为 ({u,x_1,x_2,dots ,x_k,v }),那么如果要将 (u,v) 两点连接,那么连接的边权为:(u igoplus v),即两点的点权的异或和。连通的两点同样成立。

    异或最小生成树解法

    已知权值(点权、边权)小于 (2^{len})(本题 (len=30)),则将点权值表示为 (len) 位二进制数,构造 (len) 层的字典树。于是,字典树上的 (n)叶子就表示原图的 (n) 个节点,我们要在叶子上连 (n-1) 条边使它们互相连通,且生成树边权之和最小。当然,可能存在多个点的点权相同,它们在字典树同一个叶子上、连边时权值为零,当然就要连边,且连了边也不对最小生成树答案的增加做贡献(边权为零),所以直接对点权去重即可。

    两叶子 (u,v) 连边的边权是它们点权的异或和。在字典树上,假设它们的 (LCA)(lca),则 (u,v) 的点权在从字典树根到 (lca) 表示的所有位上都相同,即异或和为 (0)

    对于字典树每个分叉点,择其两个子树中叶子较少的那个,依次取叶子(点权值)分别在另一子树中直接链状地匹配。这样,对叶子较少的子树中的各叶子分别去另一子树内匹配,能取到各自的最小异或值,最后汇总即可取得此 (LCA) 处连边时需要的总的最小异或值。

    实现上的优化技巧

    我们可以用最多 (n)vector 来记录子树中的值,并对字典树维护一个数组 ( ext{id[ ]}) 表示(i) 个(字典树)节点的子树中的叶子所代表的权值们被存储在了第 ( ext{id[i]})vector。建字典树(插入单词)时我们只需把(去重后的)每个权值记录在各自叶子所对应的某个 vector 中;在 (dfs) 回溯时再向上合并。这样,对于每个 (LCA),我们在从较小子树中取值去与另一子树匹配时,取值的步骤就是 (O(1)) 的了,更重要的是降低了代码编写难度。既然 dfs 过程不仅担负选边任务,还要担负子树叶子集的合并任务,因此各项工作当然是在回溯时进行。

    参考博客:https://blog.csdn.net/henry2k888/article/details/107621257

    代码

    #include <bits/stdc++.h>
    #define pb push_back
    using namespace std;
    typedef pair<int,int>pii;
    typedef long long ll;
    const int N=3e6+5;
    const int maxn=1e5+5;
    vector<pii>pic[N];
    int trie[N][2],a[maxn],cnt,id[N];
    ll ans;
    vector<int>value[maxn];
    void dfs(int u,int p)//边权转点权
    {
        for(int i=0;i<pic[u].size();i++)
        {
            int v=pic[u][i].second;
            if(v==p) continue;
            a[v]=(a[u]^pic[u][i].first);
            dfs(v,u);
        }
    }
    void add(int x,int y)
    {
        int rt=1;
        for(int i=29;i>=0;i--)
        {
            int t=((x>>i)&1);
            if(trie[rt][t]==0)
                trie[rt][t]=++cnt;
            rt=trie[rt][t];
        }
        id[rt]=y;
        value[y].pb(x);
    }
    int matching(int x,int rt,int d)
    {
        int xor1=(1<<(d-1));
        for(int i=d-2;i>=0;i--)//注意-2
        {
            int t=((x>>i)&1);
            if(trie[rt][t]>0)//能匹配就匹配
                rt=trie[rt][t];
            else
            {//不匹配,并记录结果
                rt=trie[rt][1-t];
                xor1|=(1<<i);
            }
        }
        return xor1;
    }
    void solve(int rt,int d)//d表示二进制的第几位
    {
        if(trie[rt][0]>0) solve(trie[rt][0],d-1);
        if(trie[rt][1]>0) solve(trie[rt][1],d-1);
        if(trie[rt][0]>0&&trie[rt][1]>0)//当前点为一个LCA
        {
            int min_xor=(1<<30);
            if(value[id[trie[rt][0]]].size()<value[id[trie[rt][1]]].size())
            {
                for(int i=0;i<value[id[trie[rt][0]]].size();i++)//从小的子树中选择
                {
                    int value1=value[id[trie[rt][0]]][i];
                    int xor1=matching(value1,trie[rt][1],d);
                    if(xor1<min_xor) min_xor=xor1;
                    //把点rt的子树的所有叶子节点的点权值合并在一起
                    value[id[trie[rt][1]]].pb(value1);
                }
                id[rt]=id[trie[rt][1]];//把记录转到rt点
            }
            else
            {
                for(int i=0;i<value[id[trie[rt][1]]].size();i++)
                {
                    int value1=value[id[trie[rt][1]]][i];
                    int xor1=matching(value1,trie[rt][0],d);
                    if(xor1<min_xor) min_xor=xor1;
                    value[id[trie[rt][0]]].pb(value1);
                }
                id[rt]=id[trie[rt][0]];
            }
            ans+=min_xor;
        }
        else
        {//直接转移,不用合并
            if(trie[rt][0]>0||trie[rt][1]>0)
                id[rt]=id[trie[rt][0]+trie[rt][1]];
        }
    }
    int main()
    {
        int n,u,w,v;
        scanf("%d",&n);
        for(int i=1;i<n;i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            pic[v].pb(make_pair(w,u));
            pic[u].pb(make_pair(w,v));
        }
        a[0]=0;
        dfs(0,-1);
        sort(a,a+n);//便于处理重复的点
        //for(int i=0;i<n;i++) cout<<a[i]<<endl;
        cnt=1;
        add(a[0],0);
        for(int i=1;i<n;i++)
        {
            if(a[i]!=a[i-1])
                add(a[i],i);
        }
        ans=0;
        solve(1,30);
        printf("%lld
    ",ans);
        return 0;
    }
    
  • 相关阅读:
    Python实现天数倒计时计算
    pandas 的数据结构Series与DataFrame
    在python中使用静态方法staticmethod
    python 中对list做减法操作
    推荐系统之 BPR 算法及 Librec的BPR算法实现【1】
    机器学习中的 ground truth
    PyCharm 默认运行 unittest
    Python的copy()与deepcopy()区别
    MySQL中Decimal类型和Float Double的区别 & BigDecimal与Double使用场景
    Spring Boot 返回 JSON 数据,一分钟搞定!
  • 原文地址:https://www.cnblogs.com/1024-xzx/p/13641761.html
Copyright © 2011-2022 走看看