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. Vue3学习(七)之 列表界面数据展示

    一.前言 昨晚可能是因为更新完文章后,导致过于兴奋睡不着(写代码确实太容易让人兴奋了),结果两点多才睡着,大东北果然还是太冷了. 不知道是不是因为膝盖和脚都是冰凉的,所以才导致很晚才能入睡? 刚眯了一 ...

  2. 注解,@Qualifier+@Autowired 和 @Resource

    摘要: 项目中,对于AOP的使用,就是通过用注解来注入的. 更改之前的注解,是使用:@Qualifier+@Autowired   但是,通过这样注解,在项目启动阶段,需要自动扫描的过程是非常缓慢的, ...

  3. 欧姆龙plc通讯协议格式

    欧姆龙CPM1A型plc与上位计算机通信的顺序是上位机先发出命令信息给PLC,PLC返回响应信息给上位 机.每次通信发送/接受的一组数据称为一"帧".帧由少于131个字符的数据构成 ...

  4. 联赛膜你测试20 T1 Simple 题解 && NOIP2017 小凯的疑惑 题解(赛瓦维斯特定理)

    前言: 数学题,对于我这种菜B还是需要多磨啊 Simple 首先它问不是好数的数量,可以转化为用总数量减去是好数的数量. 求"好数"的数量: 由裴蜀定理得,如果某个数\(i\)不能 ...

  5. 21.6.29 test

    \(NOI\) 模拟赛 \(T1\) 正解是个题解难以理解的数论,结果是组合数相加.暴力分拿满了,尝试打了 \(20*20\) 的表,最后大概打出了个三角形的表,并且帮我找到了一些性质.\(45\)p ...

  6. TCP 拥塞窗口原理

    学过网络相关课程的,都知道TCP中,有两个窗口: 滑动窗口(在我们的上一篇文章中有讲),接收方通过通告发送方自己的可以接受缓冲区大小(这个字段越大说明网络吞吐量越高),从而控制发送方的发送速度. 拥塞 ...

  7. Java并发:Condition接口

    Condition 接口与 Lock 配合实现了等待 / 通知模式,这个和 Object 的监视器方法(wait.notify.notifyAll 等方法)一样,都是实现了等待 / 通知模式,但这两者 ...

  8. python pip whl安装和使用

    转载:https://www.cnblogs.com/klb561/p/9271322.html 1 python的安装 首先,从python的官方网站 www.python.org下载需要的pyth ...

  9. 面试官问我JVM内存结构,我真的是

    面试官:今天来聊聊JVM的内存结构吧? 候选者:嗯,好的 候选者:前几次面试的时候也提到了:class文件会被类加载器装载至JVM中,并且JVM会负责程序「运行时」的「内存管理」 候选者:而JVM的内 ...

  10. Windows 2008 R2 NTP 时钟同步配置

    一.配置 本地组策略 a.windows+R 或  "开始菜单"  | "运行"  ,打开运行窗口. 输入gpedit.msc打开本地组策略 b.在 本地组策略 ...