牛客第五场 B Graph最小异或生成树

本文涉及的产品
可观测监控 Prometheus 版,每月50GB免费额度
简介: 这道题涉及到最小异或生成树,要理解这个首先要明白 01字典树关于01字典树呢,先来一道板子题hdu4825 ==》不方便跳转的同学们可以看下面的题Problem DescriptionZeus 和 Prometheus 做了一个游戏,Prometheus 给 Zeus 一个集合,集合中包含了N个正整数,随后 Prometheus 将向 Zeus 发起M次询问,每次询问中包含一个正整数 S ,之后 Zeus 需要在集合当中找出一个正整数 K ,使得 K 与 S 的异或结果最大。Prometheus 为了让 Zeus 看到人类的伟大,随即同意 Zeus 可以向人类求助。

这道题涉及到最小异或生成树,要理解这个首先要明白 01字典树

关于01字典树呢,先来一道板子题hdu4825 ==》

不方便跳转的同学们可以看下面的题


Problem Description


Zeus 和 Prometheus 做了一个游戏,Prometheus 给 Zeus 一个 集合,集合中包含了N个正整数,随后 Prometheus 将向 Zeus 发起M次询问,每次询问中包含一个正整数 S ,之后 Zeus 需要在集合当中找出一个正整数 K ,使得 K 与 S 的异或结果最大。Prometheus 为了让 Zeus 看到人类的伟大,随即同意 Zeus 可以向人类求助。你能证明人类的智慧么?


Input


输入包含若干组测试数据,每组测试数据包含若干行。

输入的第一行是一个整数T(T < 10),表示共有T组数据。

每组数据的第一行输入两个正整数N,M(1<=N,M<=100000),接下来一行,包含N个正整数,代表 Zeus 的获得的集合,之后M行,每行一个正整数S,代表 Prometheus 询问的正整数。所有正整数均不超过2^32。


Output


对于每组数据,首先需要输出单独一行”Case #?:”,其中问号处应填入当前的数据组数,组数从1开始计算。

对于每个询问,输出一个正整数K,使得K与S异或值最大。


Sample Input


2
3 2
3 4 5
1
5
4 1
4 6 5 6
3


Sample Output


Case #1:
4
3
Case #2:
4


Source


2014年百度之星程序设计大赛 - 资格赛


题意:


开始先给你一n个数,然后后面有m个询问,问当前询问的数异或上之前的 n 个数中的哪一个值更大。


这就十分体现出了01字典树的优势,将开始的 n 个数按照二进制放到树中,然后在后面的 m 次查询的时候,选出这个数按照二进制每一位和之前存入树里面的 n 个数中每一位尽可能不同的数


这个题有个坑,int是在231范围内,但是这里有句话:所有正整数均不超过2^32。,因此就用long long了叭


code:


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){ll c = getchar(),Nig = 1,x = 0;while(!isdigit(c) && c!='-')c = getchar();
    if(c == '-')Nig = -1,c = getchar();
    while(isdigit(c))x = ((x<<1) + (x<<3)) + (c^'0'),c = getchar();
    return Nig*x;}
#define read read()
const int maxn=3e6+7;
int gen;
int tree[maxn][2];
ll val[maxn];
void _add(ll x){
    int pos=0;
    for(int i=32;i>=0;i--){
        int op=(x >> i) & 1;
        if(!tree[pos][op]) tree[pos][op] = ++gen;
        pos = tree[pos][op];
    }
    val[pos] = x;///当前位置存放的数是 x
}
ll _find(ll x){
    int pos=0;
    for(int i=32;i>=0;i--){
        int op = (x >> i) & 1;
        ///异或操作相同为零不同为1
        if(tree[pos][op^1]) pos=tree[pos][op^1];
        else pos = tree[pos][op];
    }
    return val[pos];///返回当前位置的值
}
int n,m;
int main()
{
    int T=read;
    int cnt=0;
    while(T--){
        cnt++;
        printf("Case #%d:\n",cnt);
        gen=0;
        for(int i=0;i<=n;i++) val[i]=0;
        memset(tree,0,sizeof(tree));
        n=read,m=read;
        for(int i=1;i<=n;i++) {
            ll tt=read;
            _add(tt);
        }
        for(int i=1;i<=m;i++){
            ll ask=read;
            printf("%lld\n",_find(ask));
        }
    }
    return 0;
}


如果还是有的地方不是太懂的话,直接直接跳转 大佬博客 或者是 这篇博客 奉上 学长的博客

到这里其实就可以发现01字典树就是用来求异或最值问题的一种很巧妙的解题方法,算法也十分高效


再看一道这样的题

地址 或者是 地址


G. Xor-MST


time limit per test2 seconds

memory limit per test256 megabytes

inputstandard input

outputstandard output

You are given a complete undirected graph with n vertices. A number a i is assigned to each vertex, and the weight of an edge between vertices i and j is equal to a i xor a j.


Calculate the weight of the minimum spanning tree in this graph.


Input


The first line contains n (1 ≤ n ≤ 200000) — the number of vertices in the graph.


The second line contains n integers a 1, a 2, …, a n (0 ≤ a i < 230) — the numbers assigned to the vertices.


Output


Print one number — the weight of the minimum spanning tree in the graph.


Examples


inputCopy


5
1 2 3 4 5


outputCopy


8


inputCopy


4
1 2 3 4


outputCopy


8

首先解释一波题目大意:

给出 n 个点,每个点有个点权,对于之前给出的 n 个数中如果对应的分别是a[ 1 ] ~ a[ n ],而且连接的两个边是i 和 j 的情况下,那么这个边权就是a[ i ] ^ a[ j ]

题目要求是找到最小的异或生成树

写到这里,纪念下我的第一个跨天的博客诞生

对于上面那个体的代码,听了光光学长讲的,自己写了写,感觉没什么问题,然后非常成功的

微信图片_20220531140929.png

然后发现问题之后

微信图片_20220531141004.png

照着学长的374ms还是有一定的差距

首先奉上 学长的博客


code:


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){ll c = getchar(),Nig = 1,x = 0;while(!isdigit(c) && c!='-')c = getchar();
    if(c == '-')Nig = -1,c = getchar();
    while(isdigit(c))x = ((x<<1) + (x<<3)) + (c^'0'),c = getchar();
    return Nig*x;}
#define read read()
const int maxn=3e6+7;
ll a[maxn];
int n;
struct node{
    int gen;
    int tree[maxn][2];///存树
    void init(){///初始化
        gen=0;
    }
    int left[maxn],right[maxn];
    void _add(ll x,int id){
        int rt = 0;/// 默认从0开始
        for(int i = 32;i >= 0;i --){
            int op = (x >> i) & 1;
            if(!tree[rt][op]) tree[rt][op] = ++gen;
            rt = tree[rt][op];/// 根转移过去
            if(!left[rt]) left[rt] = id;
            right[rt] = id;
        }
    }
    ///询问根rt下从pos开始与 x 异或得到的最小值
    ll _RetPos(int rt,int pos,ll x){
        ll ret=0;
        for(int i = pos;i>=0;i--){
            int op=(x >> i) & 1L;/// ill?
            if(tree[rt][op]) rt = tree[rt][op];
            else{
                rt = tree[rt][!op];
                ret += (1L << i);
            }
        }
        return ret;
    }
    /// 分治
    ll _Divide(int rt,int pos){
        ///在根rt下,左右两棵子树进行遍历
        if(tree[rt][0]&&tree[rt][1]){///都存在的的情况下进行合并
            ll mi = 0x3f3f3f3f;
            /// 左右子树
            int x = tree[rt][0],y = tree[rt][1];
            for(int j = left[x];j <= right[x];j ++){///遍历当前根节点下id范围
                mi = min(mi,_RetPos(y,pos - 1,a[j])+(1L << pos));
            }
            return mi+_Divide(tree[rt][0],pos - 1)+_Divide(tree[rt][1],pos - 1);
        }
        else if(tree[rt][0]){/// 仅是左面有 && !tree[rt][1]
            return _Divide(tree[rt][0],pos - 1);
        }
        else if(tree[rt][1]){/// 仅是右面有!tree[rt][0] &&
            return _Divide(tree[rt][1],pos - 1);
        }
        /// 前面的都不符合
        return 0L;
    }
}wuyt;
int main()
{
    wuyt.init();
    n = read;
    for(int i=1;i<=n;i++){
        a[i] = read;
        ///wuyt._add(a[i],i);
    }
    /// 先排序后插入
    sort(a + 1,a + 1 + n);
    for(int i = 1;i <= n;i ++){
        wuyt._add(a[i],i);
    }
    printf("%lld\n",wuyt._Divide(0,32));
    return 0;
}
/**
5
1 2 3 4 5
8
4
1 2 3 4
8
**/


01字典树的插入是没有顺序的,可以先将输入的数进行排序,然后进行插入,再插入的时候加上 id 这样一来,在后面 分治操作 _Divide()的时候就十分简便了


步入正题:


牛客第五场B Graph 链接

微信图片_20220531141159.png


输入


6
0 1 1
1 2 4
1 3 3
0 4 5
0 5 2


输出


7

官方题解:

微信图片_20220531141331.png


code:


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){ll c = getchar(),Nig = 1,x = 0;while(!isdigit(c) && c!='-')c = getchar();
    if(c == '-')Nig = -1,c = getchar();
    while(isdigit(c))x = ((x<<1) + (x<<3)) + (c^'0'),c = getchar();
    return Nig*x;}
#define read read()
const int maxn=3e6+7;
ll a[maxn];
int n,ct=1;
struct node{
    int gen;
    int tree[maxn][2];///存树
    void init(){///初始化
        gen=0;
    }
    int left[maxn],right[maxn];
    void _add(ll x,int id){
        int rt = 0;/// 默认从0开始
        for(int i = 32;i >= 0;i --){
            int op = (x >> i) & 1;
            if(!tree[rt][op]) tree[rt][op] = ++gen;
            rt = tree[rt][op];/// 根转移过去
            if(!left[rt]) left[rt] = id;
            right[rt] = id;
        }
        ///val[pos] = x;///当前位置存放的数是 x
    }
    ///询问根rt下从pos开始与 x 异或得到的最小值
    ll _RetPos(int rt,int pos,ll x){
        ll ret=0;
        for(int i = pos;i>=0;i--){
            int op=(x >> i) & 1L;/// ill?
            if(tree[rt][op]) rt = tree[rt][op];
            else{
                rt = tree[rt][!op];
                ret += (1L << i);
            }
        }
        return ret;
    }
    /// 分治
    ll _Divide(int rt,int pos){
        ///在根rt下,左右两棵子树进行遍历
        if(tree[rt][0]&&tree[rt][1]){///都存在的的情况下进行合并
            ll mi = 0x3f3f3f3f;
            /// 左右子树
            int x = tree[rt][0],y = tree[rt][1];
            for(int j = left[x];j <= right[x];j ++){///遍历当前根节点下id范围
                mi = min(mi,_RetPos(y,pos - 1,a[j])+(1L << pos));
            }
            return mi+_Divide(tree[rt][0],pos - 1)+_Divide(tree[rt][1],pos - 1);
        }
        else if(tree[rt][0]){/// 仅是左面有 && !tree[rt][1]
            return _Divide(tree[rt][0],pos - 1);
        }
        else if(tree[rt][1]){/// 仅是右面有!tree[rt][0] &&
            return _Divide(tree[rt][1],pos - 1);
        }
        /// 前面的都不符合
        return 0L;
    }
}wuyt;
struct Edge{
    int v,next;
    ll val;/// 边权
}edge[maxn];
int  head[maxn];
void _AddEdge(int u,int v,ll val){
    edge[++ct] = Edge{v, head[u] , val};
    head[u] = ct;///链式前向星存图
}
void DFS(int u,int zx,ll val){
    a[u] = val;///
    for(int j = head[u];j; j=edge[j].next){
        int temp = edge[j].v;
        if(temp == zx) continue ;
        DFS(temp,u,val^edge[j].val);
    }
}
int main()
{
    wuyt.init();
    n = read;
    for(int i=1;i<n;i++){
        ll x=read,y=read,val=read;
        x+=1,y+=1;
        _AddEdge(x,y,val);
        _AddEdge(y,x,val);
    }
    /// 先排序后插入
    DFS(1,1,0);///此时a[]已经存入
    sort(a + 1,a + 1 + n);
    for(int i = 1;i <= n;i ++){
        wuyt._add(a[i],i);
    }
    printf("%lld\n",wuyt._Divide(0,32));
    return 0;
}
/**
6
0 1 1
1 2 4
1 3 3
0 4 5
0 5 2
**/



目录
相关文章
|
6月前
|
JSON Serverless API
阿里云百炼流程迁移工作流编排指导文档
本文介绍了大模型服务平台百炼的“流程”功能将于2025年11月15日下线,建议用户尽快迁移至全新升级的工作流编排功能。通过对比流程与工作流在节点数量、发布渠道、音视频互动等方面的能力,展示了工作流的优势,并提供了详细的迁移教学案例,包括开始节点、大模型节点、判断节点和输出节点的具体操作步骤。用户可参考文章完成迁移。
773 3
|
存储 安全 网络安全
云计算时代的网络安全:挑战与机遇
【9月更文挑战第28天】随着云计算技术的飞速发展,越来越多的企业和个人将数据和服务迁移到云端。然而,这也带来了前所未有的网络安全挑战。本文将深入探讨云计算环境下的网络安全问题,包括云服务的安全风险、信息安全的重要性以及如何应对这些挑战。我们将通过实例和案例分析,揭示在云计算时代下,如何确保数据的安全性和隐私性,并展望网络安全技术的未来发展方向。
300 68
基于电压矢量变换的锁相环simulink建模与仿真
本课题基于电压矢量变换的锁相环(PLL)在MATLAB2022a中进行Simulink建模与仿真。该模型通过ab坐标变换和低通滤波,实现对三相电网电压的快速准确锁相,尤其在电压不平衡条件下表现优异。系统仿真结果显示,PLL能有效提取基波正序电压分量,适用于负序电压前馈控制及双序电流环控制策略。相比传统单相PLL,该方法更稳健地应对电网不平衡和频率突变,广泛应用于电力质量监测、三相PFC电路控制及分布式发电系统的并网控制等领域。
|
开发框架 Rust 前端开发
Tauri:现代桌面应用的创新者
Tauri:现代桌面应用的创新者
478 0
|
关系型数据库 MySQL Shell
MySQL数据库一键安装脚本,适合任何版本
MySQL数据库一键安装脚本,适合任何版本
714 2
|
数据采集 监控 中间件
Scrapy入门到放弃03:理解settings配置,监控scrapy引擎|8月更文挑战
Scrapy入门到放弃03:理解settings配置,监控scrapy引擎|8月更文挑战
186 0
|
存储 算法 Unix
虚拟内存管理
虚拟内存管理
247 0
|
机器学习/深度学习 JavaScript 网络安全
【网络安全】实操XSS订单系统漏洞(利用盲打)
实操XSS订单系统漏洞(利用盲打)
455 0
【网络安全】实操XSS订单系统漏洞(利用盲打)
|
JavaScript 前端开发 算法
深入理解虚拟DOM:原理、优势与实践
深入理解虚拟DOM:原理、优势与实践
1539 0
|
存储 运维 Java
JUC第六讲:ThreadLocal/InheritableThreadLocal详解/TTL-MDC日志上下文实践
JUC第六讲:ThreadLocal/InheritableThreadLocal详解/TTL-MDC日志上下文实践
537 0