Graph【XOR-MST转换]

题意

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

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

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

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

\(2\leq N \leq 10^5,0\leq 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 \bigoplus v\),即两点的点权的异或和。连通的两点同样成立。

异或最小生成树解法

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

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

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

实现上的优化技巧

我们可以用最多 \(n\)vector 来记录子树中的值,并对字典树维护一个数组 \(\text{id[ ]}\) 表示\(i\) 个(字典树)节点的子树中的叶子所代表的权值们被存储在了第 \(\text{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\n",ans);
    return 0;
}
posted @ 2020-09-09 20:51  xzx9  阅读(128)  评论(0编辑  收藏  举报