O(1)判断两点之间是否有边

问题描述

给定一张 \(n\) 个点,\(m\) 条边的有向图。

多次询问,要求每次 \(\mathcal{O}(1)\) 判断两点之间是否有边(你可以忽略输入、输出等问题)。

数据范围:\(2\leq n\leq 4\times 10^5\),\(0\leq m\leq 8\times 10^5\)。

空间限制:\(512\texttt{MB}\)。

做法

朴素做法有三种:

  • 对每个点 \(u\),用一个 \(\texttt{vector}\) 存从它出发的边。将这些边按另一端点的大小排序。每次查询时,在 \(u\) 的 \(\texttt{vector}\) 里二分查找。这样单次询问的时间复杂度是 \(\mathcal{O}(\log n)\) 的。如果对每个点维护一个 \(\texttt{map}\) 或 \(\texttt{set}\),本质是一样的。
  • 用一个二维 \(\texttt{bool}\) 型数组 \(\texttt{a[u][v]}\),表示点 \(u, v\) 之间是否有边。这样单次询问时间复杂度是 \(\mathcal{O}(1)\) 的,但是空间复杂度高达 \(\mathcal{O}(n^2)\),无法承受。
  • 哈希。本文不讨论。

考虑将前两种做法结合。

设 \(x = 11\)。把每 \(2^x\) 个点分为一类。这样共有 \(\frac{n}{2^x}\) 类。用一个大小为 \(\frac{n^2}{2^x}\) 的数组,就能实现判断:每个点向每一类点之间是否有连边。

如果一个点 \(u\) 向某一类点 \(t\) 之间有连边,我们称之为一个“事件”。容易发现,事件至多只有 \(m\) 个

考虑每个事件,它对应的入点至多只有 \(2^x\) 个。将这 \(2^x\) 个点再分类。把每 \(2^6\) 个点分为一类,会分出 \(2^{x - 6}\) 类。每一类点里编号都小于 \(2^6 = 64\)。一个 \(\texttt{unsigned long long}\) 有 \(64\) 位,所以刚好可以用一个 \(\texttt{unsigned long long}\) 描述其状态。

在上述做法里,我们总共需要 \(\frac{n^2}{2^x}\) 个 \(\texttt{int}\),和 \(m\cdot 2^{x - 6}\) 个 \(\texttt{unsigned long long}\)。为了估算方便,不妨假设 \(m = 2n\)。那么所需的字节数是:\(4\cdot \frac{n^2}{2^x} + 8\cdot 2n\cdot 2^{x - 6}\),令他们相等,解得 \(x = 11\) 时该式取到最小值。刚好 \(500\texttt{MB}\) 不到。

参考代码:

const int MAXN = 4e5, MAXM = 8e5;
const int FULL5 = (1 << 5) - 1;
const int FULL6 = (1 << 6) - 1; int b1[MAXN + 5][MAXN / (1 << 11) + 5], cnt_b1;
ull b2[MAXM + 5][FULL5 + 1]; void add_edge(int u, int v) {
if (!b1[u][v >> 11]) b1[u][v >> 11] = ++cnt_b1;
b2[b1[u][v >> 11]][(v >> 6) & FULL5] |= 1ull << (v & FULL6);
}
bool have_edge(int u, int v) {
if (!b1[u][v >> 11]) return false;
return b2[b1[u][v >> 11]][(v >> 6) & FULL5] & (1ull << (v & FULL6));
}

另外,\(n\leq 2\times 10^5\),\(m\leq 4\times 10^5\) 时,上述代码只需要改变 MAXNMAXM 的值,其他参数不变,空间消耗就降到 \(171\texttt{MB}\) 了。

进一步的思考

上述做法里,我们只分了两层,这是为了介绍该算法的核心思路。其实,如果不考虑时间上的常数,我们还可以分更多层,以此来进一步优化我们的空间消耗。

例如,在 \(n\leq 10^6\),\(m\leq 2\times 10^6\) 时,如果分四层,则空间消耗仅需 \(360\texttt{MB}\)。代码如下:

const int MAXN = 1e6, MAXM = 2e6;
const int FULL3 = (1 << 3) - 1;
const int FULL6 = (1 << 6) - 1; int b1[MAXN + 5][MAXN / (1 << 15) + 5], cnt_b1;
int b2[MAXM + 5][1 << 3], cnt_b2;
int b3[MAXM + 5][1 << 3], cnt_b3;
ull b4[MAXM + 5][1 << 3]; void add_edge(int u, int v) {
if (!b1[u][v >> 15])
b1[u][v >> 15] = ++cnt_b1;
int id1 = b1[u][v >> 15]; if (!b2[id1][(v >> 12) & FULL3])
b2[id1][(v >> 12) & FULL3] = ++cnt_b2;
int id2 = b2[id1][(v >> 12) & FULL3]; if (!b3[id2][(v >> 9) & FULL3])
b3[id2][(v >> 9) & FULL3] = ++cnt_b3;
int id3 = b3[id2][(v >> 9) & FULL3]; b4[id3][(v >> 6) & FULL3] |= 1ull << (v & FULL6);
}
bool have_edge(int u, int v) {
if (!b1[u][v >> 15])
return false;
int id1 = b1[u][v >> 15]; if (!b2[id1][(v >> 12) & FULL3])
return false;
int id2 = b2[id1][(v >> 12) & FULL3]; if (!b3[id2][(v >> 9) & FULL3])
return false;
int id3 = b3[id2][(v >> 9) & FULL3]; return b4[id3][(v >> 6) & FULL3] & (1ull << (v & FULL6));
}

之所以能不断向下分层,而且使空间消耗奇迹般地减小,它的核心是:不论怎么分,每层的事件都至多只有 \(m\) 个。

把这种思路推到极致,如果分出 \(\log n\) 层,则时间复杂度将回到 \(\mathcal{O}(\log n)\),此时相当于给每个点 \(u\) 开了一个 \(\text{01-Trie}\)。

我们只需要记住,层数越多,时间上消耗越大,空间上消耗越小。本算法的精髓就是在它们之间找到符合实际需求的平衡点。

O(1)判断两点之间是否有边的更多相关文章

  1. Floyd算法——计算图中任意两点之间的最短路径

    百度百科定义:传送门 一.floyd算法 说实话这个算法是用来求多源最短路径的算法. 算法原理: 1,从任意一条单边路径开始.所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大. 2,对 ...

  2. POJ 3660 Cow Contest 任意两点之间的关系 Floyd

    题意:牛之间有绝对的强弱,给出一些胜负关系,问有多少头牛可以确定其绝对排名. #include <iostream> #include <cstdio> #include &l ...

  3. sql server2008根据经纬度计算两点之间的距离

    --通过经纬度计算两点之间的距离 create FUNCTION [dbo].[fnGetDistanceNew] --LatBegin 开始经度 --LngBegin 开始维度 --29.49029 ...

  4. C#面向对象思想计算两点之间距离

    题目为计算两点之间距离. 面向过程的思维方式,两点的横坐标之差,纵坐标之差,平方求和,再开跟,得到两点之间距离. using System; using System.Collections.Gene ...

  5. (转)c# math 计算两点之间的角度公式

    计算两点之间的角度公式是: 假设点一(X1,Y1),点二(X2,Y2) double angleOfLine = Math.Atan2((Y2 - Y1), (X2 - X2)) * 180 / Ma ...

  6. 2D和3D空间中计算两点之间的距离

    自己在做游戏的忘记了Unity帮我们提供计算两点之间的距离,在百度搜索了下. 原来有一个公式自己就写了一个方法O(∩_∩)O~,到僵尸到达某一个点之后就向另一个奔跑过去 /// <summary ...

  7. c++ 算法 栅格中两点之间连线

    屏幕划线,通过平面坐标系实现,基本组成是一个一个的点,起点为A,终点为B 本文的算法,可以实现平面栅格中,指定的A,B两点之间进行连线(代码中仅打印了两点间需要画出的坐标点) #include < ...

  8. 求两点之间距离 C++

    求两点之间距离(20 分) 定义一个Point类,有两个数据成员:x和y, 分别代表x坐标和y坐标,并有若干成员函数. 定义一个函数Distance(), 用于求两点之间的距离.输入格式: 输入有两行 ...

  9. 图上两点之间的第k最短路径的长度 ACM-ICPC 2018 沈阳赛区网络预赛 D. Made In Heaven

    131072K   One day in the jail, F·F invites Jolyne Kujo (JOJO in brief) to play tennis with her. Howe ...

随机推荐

  1. 【c++ Prime 学习笔记】第8章 IO库

    C++语言不直接处理输入输出,而是通过标准库中的一组类来处理IO 1.2节介绍的IO库: istream(输入流)类型,提供输入 ostream(输出流)类型,提供输出 cin,是istream对象, ...

  2. 回应:Alpha深度评测

    零.说明 本篇博客是针对博客沉舟侧畔千帆过,病树前头万木春--对[题士]产品的深度测评与解析的回应,用以说明『题士』开发团队的观点.改进计划等 感谢HansBug.CookieLau助教及各位老师.测 ...

  3. PM技术分享——《构建之法》初步实践

    软件理论 软件=程序+软件工程:软件开发活动(构建管理.源代码管理.软件设计.软件测试.项目管理)相关的内容的完成,才能完成把整个程序转化成为一个可用的软件的过程. 软件企业=软件+商业模式 软件开发 ...

  4. [对对子队]团队任务拆解Alpha

    Alpha阶段主要目标 完成游戏场景的基本实现(不要求美术资源) 完成游戏UI的基本实现(不要求美术资源) 制作第一部分的关卡(顺序语句,制作3-5关) 完成第一部分关卡和游戏基本逻辑的测试 任务分解 ...

  5. RMQ、ST表

    ST表 \(\text{ST}\) 表是用于解决可重复贡献问题的数据结构. 可重复贡献问题:区间按位和.区间按位或.区间 \(\gcd\) .区间最大.区间最小等满足结合律且可重复统计的问题. 模板预 ...

  6. SkyWalking配上告警更优秀

    前言 对于监控系统来说,不可能让人一直盯着监控看板,而更多的是以自动提醒的方式,比如邮件.短信或微信推送等,当达到或超出预设的告警指标时,就自动发送消息提醒,下面就来说说如何配置SkyWalking的 ...

  7. 『学了就忘』Linux基础 — 11、通过setup工具配置Linux系统IP地址

    目录 1.setup命令介绍 2.使用setup命令配置IP (1)执行setup命令 (2)进入图形化配置界面 (3)选择配置IP还是DNS (4)选择要配置的网卡 (5)进入IP地址配置页面 (6 ...

  8. JS控制文本框禁止输入特殊字符

    JS 控制不能输入特殊字符<input type="text" class="domain" onkeyup="this.value=this. ...

  9. Python pip 和pip3区别 联系

    python 有python2和python3的区别 那么pip也有pip和pip3的区别 大概是这样的 pip是python的包管理工具,pip和pip3版本不同,都位于Scripts\目录下: 如 ...

  10. Vue-cli4.xPC端项目Rem适配

    适配准备 安装 (amfe-flexible) 和(postcss-px2rem) 1, 安装依赖并在main.js中引入该依赖 npm i amfe-flexible import "am ...