原题链接

题目描述:

个人分析:从输入数据看,要处理的元素个数(n)没有到达 10^9 或 10^8 级,或许可以使用暴力?但是稍微计算一下,有 10^5 * (10^5 - 1) / 2 = 10^10 / 2

个结果,说明至少运算那么多次。假设每次运算使用1ns(CPU运算速度纳秒为单位),貌似没有超时,但是加上内存分配,数组越界检查等时间,大概率超时。

需要有一种办法减少重复运算,首先需要了解异或运算的特性:(以下讨论均是正数情况,因为题目的输入范围均是正数)

a 和 b 从高位开始逐位异或,只有两者相应位上的数不同,结果才能是1。

a 和 b 某一位上 异或的结果如果是1 ,并且待比较数上相应位的数是0,说明 a 和 b 异或的结果必定大于待比较数

因为异或结果在高位上大于待比较数,低位就不需要比较了。也就是说,a 和 任何 前缀与 b 相同的数异或,结果都会大于待比较数,因为异或出来的结果

必然和 c 有共同的前缀,有这样的前缀的话,就比如比待比较数大

     

于是得到思路:

如果找到一种能对相同前缀元素进行计数的数据结构,就可以直接返回符合前缀条件的元素个数,减少运算。

字典树:Trie Tree 正好是满足预期的数据结构

我的思路 :

题目要输入 n 个数,求出 这 n 个数两两异或后 大于 m 的结果。

或许可以先向字典树中插入一个数 A1 ,先保证数不空,而且题目中保证了输入的数的数量大于1个,所以必能有第一个数 A1 插入字典树

对于之后输入的数 Ax (x > 1),先去字典树里找有几个和 Ax 异或后结果大于 m 的数 (寻找过程见后文),然后再把 Ax 插入到字典树中。

不怕 Ax 错过 之后的 Ax+1 , Ax+2, ......,因为Ax+1, Ax+2,...... 会遇到前面已经在字典树里的Ax,异或运算可交换,a^b = b^a

伪代码: 含义是先把A1插入字典树,之后输入的Ax,都要先去树里找和 他异或大于m 的数有多少个,并且把数量进行累积

tree.insert(A1);

int total = 0;

for( input Ax ){

  total += tree.compare(Ax, m);

   tree.insert(Ax);

}

output total

 实操:(JDK 1.8)

1.打好框架:

  输入输出流

  字典树申明

import java.util.Scanner;

public class Xor {
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
TrieTree tree = new TrieTree(); while(sc.hasNext()){
int n = sc.nextInt();
int m = sc.nextInt();
long total = 0;
        //插入A1
tree.insert(sc.nextInt());
for(int i = 0; i < n - 1; i ++){
int me = sc.nextInt();
  //找出和Ax异或后结果大于m的数有多少个,并且累加
        total += tree.compare(me, m);
         //插入Ax
tree.insert(me);
}
       //输出结果
System.out.println(total);
}
}
private static class Node{
     //有多少个数有当前前缀
public int count = 1;
     //子节点
public Node[] child = new Node[2];
} private static class TrieTree{
     //根节点
Node root;
    
public TrieTree(){
//根节点不包含信息,卫星数据
       this.root = new Node();
} public int compare(int tar, int m){
} public void insert(int tar){
}
}
}

2.具体实现

  最主要的函数只有两个,insert和compare

    insert很简单,比如插入 00000110000000000000000000000000(100663296),insert忽略掉最高位符号位,因为所有输入都是正数,则我们从左往右数的

第二位开始

只需要新建节点即可,每个节点的count默认是1(因为一个节点必定在某条路径上,而这条路径代表了一个数,这个数包含从根到这个节点位置形成的前缀(假设这个节点是上图的第四个节点,那么形成的前缀就是 0000 ,00000110000000000000000000000000 包含前缀 0000 ),所以这个节点的count必定 >= 1)。

再插入 00010110000000000000000000000000(369098752)

从根节点出发,如果对应位的节点已经存在,则令其count + +,如果不存在则新建

让当前节点 now 等于 root

因为 369098752 的第二位是 0(忽略最高位符号位第一位),所以 看看 now.child[0] 是否为空,发现不为空,则让now.child[0].count ++

并且让当前节点 等于 now.child[0],接着向下执行,发现当前节点(节点1)的child[0]也不为空,也让其 count++, 依此类推,到了 now = 节点3

因为 now.child[1] (369098752的第四位为1,所以取1)为空,所以新建节点,并且count 默认 = 1

于是下面有三个count = 2 的节点,表示有两个数的路径包含这些节点

        public void insert(int tar){
Node now = root;
root.count ++;
//从第二位开始,忽略最高为第一位,所有输入都是正数,忽略最高符号位
for(int i = 30; i >= 0; i --){
//获取要插入的数的第(32 - i )位
int res = (tar >>> i) & 1;
//如果之前不存在节点,则新建节点,count 默认 = 1
if(now.child[res] == null){
now.child[res] = new Node();
now = now.child[res];
}else{
//如果之前已经有节点存在,则count++
now = now.child[res];
now.count ++;
}
}
}

  compare:

假设某次比较时,字典树如下图状态,并且输入的数Ax如图所示,被比较的数m如下图

为了方便观察,只保留 Ax 和 m 的前面几位

从根节点开始向下比较,也即从第二位开始比较

分如下情况:

1.假设Ax的当前位为 b , 我们的想法当然是想找到 树中当前层次的节点 b ^ 1 ,因为 (b ^ 1) ^ b = 1 ,这样的话,当前位异或的结果为1

如果待比较数m中的当前位为 0,那么Ax和节点 b ^ 1 的所有分支异或的结果都大于m(情况1)

如果待比较数m中的当前位为1,那么目前的比较结果和 m 尚且相等,继续比较下去(情况2)

2.假设 b ^ 1 节点不存在,那么只能委曲求全,走 节点 b,需要注意的是,

如果 m 的当前位为1,说明 m 大于我们能走的唯一路径的全部异或结果(情况3)

因为 b ^ b = 0 < 1 (m 当前位),说明节点 b 路径上的异或结果都要小于m,而且只能走节点 b 的路径,所以直接返回 0

如果 m 的当前位为0,则目前的比较结果和 m 尚且相等,继续比较下去(情况4)

需要注意的是,情况1不能直接返回节点 b ^ 1 的count,因为另一条路 虽然当前位异或结果 = 0,但是因为 m 的当前位也是0,所以异或结果不至于小于m

还要进行后继比较

另外,如果当前的节点为空,表明已经比较到叶子节点了,但是还是没有比较出个所以然,说明异或结果与m相等,没有大于m的,返回0(情况5)

      // now : 当前前缀树中,需要开始比较的节点
// tar : 将要插入的数,但是在调用insert插入之前,要和已经插入的数比较(当前前缀树)
// m : 要大于的那个树
// bit : 当前节点的儿子们表示的是对第几位的比较
public int compare(Node now, int Ax, int m, int bit){
//逐位比较
for(int i = bit; i >= 0; i --){
if(now == null){
//空节点 表示两者相同 情况5
return 0;
}
int res = (Ax >>> i) & 1;
//存在能够 XOR 出 1 的路径, 情况1或2
if((now.child[res ^ 1]) != null){
//如果目标的当前位是 0,说明异或结果已经小于 Ax
if(((m >>> i) & 1) == 0){
//情况1
               //但是不能单纯只返回异或结果大于m的那条路径上的分支数量
//因为当前位异或结果相等于m的那条路径上的分支可能还存在满足异或结果大于m的情况
return now.child[res ^ 1].count + compare(now.child[res], tar, m, i - 1);
} else {
    //情况2
              //异或结果相等 接着找
now = now.child[res ^ 1];
}
} else{
//情况3
//异或结果小于 m, 直接返回0
if(((m >>> i) & 1) == 1){
return 0;
}
//情况4
            //异或结果相等,接着找
now = now.child[res];
}
}
       //默认返回0
return 0;
}

3.结果估计

   假设输入了 10 ^ 5 个数

   每个Node对象占用内存 =

 1.没有指针压缩,且在64位机器上

 8字节markOop,8字节 Klass*,8字节数组引用,8字节int(内存对齐),共32字节

  每个数占用约32位,每位需要一个节点,且输入了 10 ^ 5 个数,总共占用内存最多 = 10 ^ 5 * 32 * 32 B  = 102 400 KB = 102 MB 左右

 但是实际上字典树中的大部分数都有相同的前缀,真实占用的内存肯定会比 102 MB少不少 (不算上栈上内存)

实际结果:

可见内存占用为 43MB 左右,比102MB小不少。

总结:字典树可以在某些 求最大异或结果或者异或结果如何如何的关于位运算的题目中使用,以减少运算次数,网络IP地址的最长前缀查找等题目同理。

字节真题 ZJ26-异或:使用字典树减少计算次数的更多相关文章

  1. NEUOJ711 异星工厂 字典树+贪心

    题意:你可以收集两个不相交区间的权值,区间权值是区间异或,问这两个权值和最大是多少 分析:很多有关异或求最大的题都是利用01字典树进行贪心,做这个题的时候我都忘了...最后是看别人代码的时候才想起来这 ...

  2. Xor Sum(讲解异或)【字典树】

    Xor Sum 题目链接(点击) Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 132768/132768 K (Java/Other ...

  3. 蓝桥杯 第三届C/C++预赛真题(9) 夺冠概率(手工计算概率)

    足球比赛具有一定程度的偶然性,弱队也有战胜强队的可能. 假设有甲.乙.丙.丁四个球队.根据他们过去比赛的成绩,得出每个队与另一个队对阵时取胜的概率表: 甲 乙 丙 丁 甲 - 0.1 0.3 0.5乙 ...

  4. 字典树(Trie)的学习笔记

    按照一本通往下学,学到吐血了... 例题1 字典树模板题吗. 先讲讲字典树: 给出代码(太简单了...)! #include<cstdio> #include<cstring> ...

  5. 817E. Choosing The Commander trie字典树

    LINK 题意:现有3种操作 加入一个值,删除一个值,询问pi^x<k的个数 思路:很像以前lightoj上写过的01异或的字典树,用字典树维护数求异或值即可 /** @Date : 2017- ...

  6. HDU6625: three arrays (字典树处理xor)

    题意:给出A数组,B数组,你可以对A和B分别进行重排列,使得C[i]=A[i]^B[i]的字典序最小. 思路:对于这类题,显然需要建立字典树,然后某种形式取分治,或者贪心.  假设现在有了两颗字典树A ...

  7. GCPC 2013_A Boggle DFS+字典树 CSU 1457

    上周比赛的题目,由于那个B题被神编译器的优化功能给卡了,就没动过这个题,其实就是个字典树嘛.当然,由于要在Boggle矩阵里得到初始序列,我还一度有点虚,不知道是用BFS还是DFS,最后发现DFS要好 ...

  8. HDU 5687 Problem C ( 字典树前缀增删查 )

    题意 : 度熊手上有一本神奇的字典,你可以在它里面做如下三个操作: 1.insert : 往神奇字典中插入一个单词 2.delete: 在神奇字典中删除所有前缀等于给定字符串的单词 3.search: ...

  9. SPOJ MAXOR (分块 || 可持久化字典树 || 异或)(好题)

    You are given a sequence A[1], A[2], ..., A[N]. (0 ≤ A[i] < 231, 1 ≤ N ≤ 12000). A query is defin ...

随机推荐

  1. 深入理解JVM(③)线程与Java的线程

    前言 我们都知道,线程是比进程更轻量级的调度执行单位,线程的引入,可以把一个进程的资源分配和执行调度分开,各个线程既可以共享进程资源调度(内存地址.文件I/O等),又可以独立调度. 线程的实现 主流的 ...

  2. node子进程(Child Process)获取硬盘分区

    node   child_process文档 child_process.exec(command[, options][, callback]) command <string> The ...

  3. 机器学习实战基础(十六):sklearn中的数据预处理和特征工程(九)特征选择 之 Filter过滤法(三) 总结

    过滤法总结 到这里我们学习了常用的基于过滤法的特征选择,包括方差过滤,基于卡方,F检验和互信息的相关性过滤,讲解了各个过滤的原理和面临的问题,以及怎样调这些过滤类的超参数.通常来说,我会建议,先使用方 ...

  4. 数据可视化之DAX篇(十五)Power BI按表筛选的思路

    https://zhuanlan.zhihu.com/p/121773967 ​数据分析就是筛选.分组.聚合的过程,关于筛选,可以按一个维度来筛选,也可以按多个维度筛选,还有种常见的方式是,利用几个特 ...

  5. c++运行程序 改变字和背景的颜色与窗口大小和位置 (c++)(windows)

    关于改变字体的颜色和背景颜色: 在#include <windows.h> 库里 0=黑色 1=蓝色 2=绿色 3=湖蓝色 4=红色 5=紫色 6=黄色 7=白色 8=灰色 9=淡蓝色 A ...

  6. 【Python学习笔记七】从配置文件中读取参数

    将一些需要更改或者固定的内容存放在配置文件中,通过读取配置文件来获取参数,这样修改以及使用起来比较方便 1.首先是配置文件的写法如下一个environment.ini文件: 里面“[]”存放的是sec ...

  7. 题解 CF1354D 【Multiset】

    考试拿到题,一看,这不是权值线段树吗? 思路 使用线段树每个节点维护该区间内元素出现次数. 根据题目,对于加入.删除元素,我们可以单点修改(\(+1\).\(-1\)),对于输出,我们可 随便 遍历找 ...

  8. 设计模式:singleton模式

    目的:限制类的实例个数只能是一个 例子: #define AGT_DECLARE_SINGLETON(ClassName) \ public: \ static ClassName *Instance ...

  9. 本周六 Apache DolphinScheduler & Doris 将联合线上 Meetup

    活动背景 2020年,大数据成为国家基建的一个重要组成,大数据在越来越多的领域展现威力.随着大数据的应用场景越来越多,大家对数据的响应速度和数据加工工作流的方便程度也提出了更高的要求.在这种背景下,相 ...

  10. DJANGO-天天生鲜项目从0到1-005-FastDFS与Nginx打造自定义文件存储系统

    本项目基于B站UP主‘神奇的老黄’的教学视频‘天天生鲜Django项目’,视频讲的非常好,推荐新手观看学习 https://www.bilibili.com/video/BV1vt41147K8?p= ...