本篇将讨论一下大致思想与环有关的图论问题,也许会涉及到一些其它的知识点,所以某些部分可能看起来已经几乎没有环什么事了。
由于环的知识点肯定没有树那么多,所以这篇博客的量肯定不及我写的树的进阶,所以就凑合凑合看吧。。
本篇博客会主要讲解以下内容:
- 环计数
- 最小环
- 欧拉图与半欧拉图
- 最短路算法与环
- 强连通分量
环计数
简单环计数
所谓简单环,就是一条不重复经过除了起点和终点以外的其它点的回路。
对于简单环计数问题,我们目前无法使用非暴力手段去解决,而是朴素地使用状态压缩。具体见例题。
对于一个环,我们可以看作是一条不经过任何重复节点的路径,然后首尾相连所形成的回路。因此我们可以先计算路径,然后判断首尾是否可以相连,如果可以就直接统计到总答案中。
由于需要判断首尾相连的可行性,我们需要让 DP 的状态中包含“路径起点”这一个信息。根据环的性质,我们不管在这个环上从哪个点出发,最终都可以得到同一个环,因此我们可以钦定环上编号最小的点为起点。
我们设 表示当前所在点为 ,当前经过的路径的点集为 的合法路径数量,且强制让 中编号最小的点为这条路径的起点。考虑加入一个新的点 ,满足 之间存在直接连边。若 且 是 中编号最小的,则这里显然可以从 直接折返到 从而形成一个环,因此 ;否则 就必然不属于 ,不然就不满足简单环的定义,同时 不能比 中所有点的编号都低,因为这样就会改变起点,进而导致答案算重,于是我们的转移就是 。
初始化为 。
不过在上述 DP 过程中,一条边 也会被算作环路,这是因为我们并没有考虑环上重边的情况。好在重边只会在一条单边上出现问题,我们可以直接减掉 条边。再考虑到一个环可以顺时针逆时针两个方向走,即我们的路径可以是沿着环朝两个方向走,因此我们最终的答案应该是 。
三元环计数
三元环中只会有 个不同的点,因此我们可以设计一些快速的算法去解决这一类计数。
在朴素算法中,我们肯定是先枚举 ,通过与 的所有连边枚举另一个点 ,再通过与 的所有连边枚举另一个点 ,若 存在连边,则 构成一个三元环。判断 是否存在连边可以在枚举到 的时候提前处理与 相邻的点并打上标记,最后直接看 是否被标记即可。考虑到每个三元环可以是 三种枚举方式,我们得到的答案还需要除以 。我们发现对于一个点 ,设其度数为 ,则 作为中间被枚举的 时会被循环 次,因此最劣时间复杂度为 。
我们希望 大的点作为 的枚举次数尽可能少。同时我们发现最终答案还要除以 这个地方显得很多余,我们可以尝试定义一种严格偏序关系,然后每次枚举 的时候保证 形成严格偏序,这样我们的答案就不用除以 。借着这个思路,我们考虑用偏序关系给边定向,对于一条边 ,若 或者 ,则这条边的方向为 ,这样每条边就能代表一个严格的偏序关系。而是所以这么设计偏序,是因为它使得 更大的点被枚举的次数减小。
在用偏序关系定向后,无向图就变成了有向图,又因为我们采用的是严格偏序,因此我们得到的实际上是一个 DAG。
设原图点数为 ,边数为 :
计算在当前这个 DAG 上暴力枚举的时间复杂度:
若 ,则根据偏序关系有 ,因此对于一个 ,它枚举另外两个 的最劣复杂度为 。要求得此时的最劣总时间复杂度,我们可以看成是容量为 的背包,有若干个物品,体积为 且 ,价值为 ,然后求能够得到的最大价值,根据性价比可以知道只选 的物品时价值取最大值,因此时间复杂度为 。
若 ,则单次枚举的最劣情况是把 条边都走了一遍,而 的 只有不超过 个,因此此时最劣复杂度为 。
综上,在 DAG 上暴力枚举的总时间复杂度最劣为 。
至此,我们就能完成无向图三元环计数了。在这道模板题中,我们构造了偏序关系将无向边转换为有向边。由此我们可以知道偏序也是可以用于一些特殊的图论问题中的,后面也会稍微讲一下偏序,等到后面再说吧。
我们考虑枚举 然后看有多少个合法的四元组 。因为题目要求了 ,我们只需要枚举以 开头的三元组,这个可以通过预处理 vector 得到。设 表示所有以 开头的三元组。
接着我们任选 ,建立一条连接 的无向边。在建出来的图中,对于每一个三元环 ,若这个三元组被给出,则 可以加上一个 构成合法四元组 。由于三元环的数量并不多,我们可以套用模板的做法,先把图中所有的三元环提取出来,然后用 map
依次检查它们是否被给出过。
为了证明复杂度,我们先引入一个结论:
结论:,其中 。
证明:将两边同时平方可得 。去掉相同项就能化简为 ,根据均值不等式 即可证明 成立。
用 表示总时间复杂度为 ,结合 以及不等式 ,我们可以知道最劣复杂度应为 。
正难则反,我们考虑分别求出至少有 条边,至少有 条边,至少有 条边的三元组数量,然后代入容斥系数就能算出答案。
对于至少有 条边的三元组,我们发现它们三个恰好可以构成一个图中三元环,因此我们 找到每个三元环,然后代入题目的式子去计算贡献。
对于至少有 条边的三元组 ,我们发现这 条边是一条恰好经过了 的短链。考虑钦定这条短链的中点为 ,我们在与 相连的点中选出任意两个不同的点 。由于我们现在求的是至少 条边的情况,因此我们不需要考虑 之间是否连边。在找出与 相连的所有点之后,把它们按照编号从小到大排序,然后对于其中的每个点 ,我们可以统计 的贡献次数:
- 若 ,则 不能产生 的贡献, 的贡献次数为 , 的贡献次数为 ,这里的 是指与 的相邻点。
- 若 ,则 不能产生 的贡献, 的贡献次数为 , 的贡献次数为 。
这些贡献次数都是可以简单算的。此外,我们还需要考虑 这个点的贡献,设 ,则 的贡献次数为 , 的贡献次数为 , 的贡献次数为 。
对于至少有 条边的三元组 ,我们只需要枚举每一条边 ,不妨设 ,然后剩下的 个点任意选,我们只需要把剩余的点分为 三个部分,每个部分分别算 的贡献,最后加上 的贡献。三个部分的计算我们可以直接前缀和。
最后算总代价数,显然第 个数对总代价的贡献为 。
容斥一下就可以了。
这道题并没有涉及到三元环计数的任何代码,但是却运用了三元环计数这个东西去证明正解的时间复杂度。
首先可以判断,如果 ,则显然我们可以只走 边,在原图上求最短路即可。否则,就考虑这样一个性质:在新图上的最短路最多只会走一条 边。证明过程很简单,我们只需要在原图上求出 的最短路并记录其路径 ,容易得知 上的任意的相邻三个点 必然满足原图上 之间不存在连边,否则我们可以直接从 走到 ,这样 为最短路就不成立。由于相邻三个点 满足 不存在连边,那么在新图上就有 之间的一条 边,结合 ,我们贪心地把 上相邻的两条 边变成一条 边,此时最多会剩下一条落单的 边。
设 表示 的边数,则答案可以为 。于是我们直接在原图上做一遍 找最短路即可。
最后还有一种情况,如果 远大于 ,那么我们肯定是希望全部走 边。假设有两条 的路径 和 ,设 表示 的边数, 表示 的边数,如果有 且 ,那么我们显然是去选择 ,因为 无论怎么缩边都会剩下一条 边。因此我们还需要对于每个点 ,求一条 的边数为偶数的最短路。这个我们依然可以在原图上做 ,只不过每次都是转移到一个与当前点最短路为 的点。
但是在这里 的复杂度是 ,因为我们有 层循环都是在枚举出边。此时我们可以考虑删去一些无用的边,假设当前我们枚举的点为 ,我们接下来二层循环分别枚举到了 (要保证 未被遍历,且与 无边),那么在处理完 之后,我们可以直接删去 这条出边,因为在第二层循环中枚举到 的只能是 的相邻点,当我们借 更新了一次之后,根据 的性质,其余点 对 的影响必然不优于 。
具体的删除操作,我们可以用链式前向星存边,每次删掉一条边就相当于在我们的链上删除一点,采用链表中的删点方式即可。
关于直接复杂度,对于不是三元环上的边 ,我们显然只会遍历 和 这两次,时间复杂度为 ,对于在三元环 上的边 ,我们最开始可能是从三元环中的另一个点 走过来的,此时遍历了这条边之后,由于三者相互有边,我们就不能删除 。每个三元环中的 条边都会被保留 次,因此遍历这些边的时间复杂度为 。
总时间复杂度为 。
四元环计数
对于一个四元组 ,如果在图 中存在边 ,则称 为一个四元环。
在四元环计数问题中,我们考虑枚举 ,这两个点之间可以有边,也可以无边,两者相互不存在限制。然后我们找出这样的一些点 满足 中存在 和 的连边,设这样的 有 个,则我们以 为对角的四元环的数量为 。
不过暴力枚举 必然是不行的,因此我们可以先按照三元环计数的给边定向的方式,将无向图转换为有向图,然后从 开始沿着有向边枚举 ,再从 开始沿着无向边枚举另一个点 ,那么我们就让 。最后我们再重新枚举 ,计算 的和。
考虑到从 枚举到 是通过无向边找的,因此我们无法保证 在严格偏序上比 大,因此我们在枚举 的时候需要额外的判断 在严格偏序上是否大于 ,如果是 大于 ,我们才会让 。
虽然四元环计数中有一层循环枚举了无向边,但时间复杂度的推导依然和三元环是类似的,也是 。
有兴趣的话可以做一下 Connected Subgraphs,这个题算是一个综合的套题,有四元环计数,有三元环计数,以及一些其余情况的计数。这里不再展开讲解了。
最小环
概念
对于一张大小为 ,边数为 的图 ,我们在其中找到的点数至少为 ,边权和最小的环,称为最小环,也称围长。
无向图最小环问题
考虑一个暴力解法。对于每一条边 ,我们尝试求出经过这一条边的最小环。于是我们在原图上删去这一条边,然后找到新图中 之间的最短路 ,那么经过 的最小环必然是 加上这条边所形成的环。时间复杂度为 。
根据上面的暴力思路,我们再考虑 Floyd 算法。在 Floyd 算法中,我们会依次循环 然后用 更新 ,假定 是从小到大枚举的,那么我们执行完第 次循环后求得的 就是除了 之外,只经过编号在 之间的点的 最短路。由此,对于一个最小环,设其中编号最大的点为 , 在环上两侧的点为 ,那么当我们 Floyd 中的 遍历完 之后,这个最小环的边权和就是 。
因此,我们只需要在 Floyd 的过程中,在执行 的循环之前,找到另外两个编号小于 的点 表示 两侧的点,然后用 更新答案即可。时间复杂度 。
有向图最小环问题
和无向图最小环问题的大致思路一样,也是使用 Floyd 算法,但是要注意到有向环的限制,即我们最小环的边权和变为 。
欧拉图与半欧拉图
概念
四个基本概念:
- 欧拉路径:经过图中所有边恰好一次的路径。
- 欧拉回路:经过图中所有边恰好一次的回路。
- 欧拉图:存在欧拉回路的图。
- 半欧拉图:存在欧拉路径但是不存在欧拉回路的图。
欧拉回路一定是欧拉路径,但是欧拉路径并不一定是欧拉回路。
判别法则
有向图
其实我们在欧拉图以及欧拉半图这个板块中最常用的是有向图。
先来思考怎么判欧拉图。设有向图中存在一条欧拉回路,且起点和终点均为 ,那么 必然有一对出边和入边对应着回路的起始边和终止边。对于每个节点 ,如果存在此时的回路已经走到了一条关于 的入边且 并不是回路的终点,那么我们必然要用另一条关于 的没有被走过的出边让回路走出 ,特殊地,若 且此时还存在未被走过的边,那么我们还是要借一条 的没有被走过的出边让回路走出去。因此无论如何,对于每个点 来说,每一条关于 的入边必然与 的出边形成一一对应关系。则:
判别有向图 是否为欧拉图的法则:若 的所有非零度点弱连通且 ,有 ,则 为欧拉图。
再来思考怎么判半欧拉图。不难发现,设一张半欧拉图图 存在一条从 到 的欧拉路径(),如果我们再在 上添加一条 的有向边,那么 就会变成一个欧拉图,此时图中的欧拉回路就是原图中 到 的欧拉路径再通过新边回到 所形成的回路。考虑加边前后的变化,其实就是 以及 。因此我们可以找到原图中所有入度不等于出度的点,若恰好有两个这样的点且其中一个是入度比出度少一,另一个是入度比出度多一,则我们可以令 为前者, 为后者,即原图中存在这样一条从 走到 的欧拉路径。
判别有向图 是否为半欧拉图的法则:若 的所有非零度点弱连通且恰好有一个点 满足 ,恰好有一个点 满足 ,剩余点都满足 ,则 为半欧拉图。
无向图
其实就是判断是否存在一种给所有边定向的方案使得原图变成欧拉图/半欧拉图。由于证明与有向图几乎类似,这里直接给出两个结论:
判别无向图 是否为欧拉图的法则:若 的所有非零度点强连通 中所有点的度数为偶数,则 为欧拉图。
判别无向图 是否为半欧拉图的法则:若 的所有非零度点弱连通 中恰好有两个点的度数为奇数,其余点的度数都是偶数,则 为半欧拉图。
由于欧拉路径也可以是欧拉回路,因此我们判断 No
的情况就是在判断原图是否为欧拉图或者半欧拉图,如果两者都不是,那么就是输出 No
。
如果原图是半欧拉图的话,我们就从 的 点出发,找一条字典序最小的欧拉路径。这个可以用 vector
对出边进行排序,然后每次遍历到当前点的时候就从最小的未被遍历的边走出去就行了。
如果原图是欧拉图的话,我们直接随机选择一个起点,然后按照上述方法 DFS 一遍就行了。
欧拉回路应用
不难发现可以多次经过重复点的环其实就是一条欧拉回路。因此我们的问题是判断这个简单无向连通图是否可以把边集分成三部分,使得三部分都能构成欧拉回路。
性质 :若一个图 可以分解为若干个欧拉回路,则 必然为欧拉图。
证明:根据无向图欧拉图判别法则,若 可以分为若干个欧拉回路,那么这若干个子图中每个点的度数必然为偶数。将所有欧拉回路合并成原图,则每个点的度数依然为偶数,因此原图为欧拉图。
由性质 ,我们就可以用无向图欧拉图判别法则,去判断给定的图是否是欧拉图,即判断是否每个点的度数 都是偶数,如果不是就输出 No
。
性质 :若一个欧拉图 中点的度数最大值为 ,则 至少可以分解出 个边集不相交的欧拉图。
证明:从这个度数为 的点 为起点找一条欧拉回路,记录回路上的点序列 。设 表示 在 中第 次出现的位置,则我们可以对于任意的 ,把 这一部分的子图分解出来,显然这个子图也是欧拉图(因为我们从 开始走完这个子图后又回到了 ,构成欧拉回路)。由于 在 中的出现次数为 ,因此我们最后分解出来的欧拉图就有 个。综上, 至少能分解出 个边集不相交的欧拉图。
由性质 ,我们可以找到图中的最大度数 ,如果 ,则原图就能分解出 个欧拉图,此时输出 Yes
。
不难发现如果一个点 的度数 为 ,那么 连接的两条边必然属于同一个欧拉回路。因此我们可以对于每个 的 ,把这两条边用并查集合并起来。最后我们只需要考虑 的情况:
- 如果 ,则根据性质 ,我们至少可以分解出两个欧拉回路,但是其余点都是 的,因此我们只能分解出两个欧拉回路,输出
No
。
- 如果 ,设这两个点为 ,我们可以找到这两个点向外的四条边 和 ,不妨设这两个四元组的元素均按编号从小到大排序,如果 ,都满足 和 被合并为了同一个集合,那么就只能分解成两个欧拉回路,输出
No
。否则必然恰好有 条不相交的,连接 的路径,这是因为我们可以从 开始走欧拉回路,就必然存在 和 这两种不相交的路径。我们考虑用这两条路径组成一个欧拉回路,将其分解出去之后,我们的每个点的度数都变为了 ,因此就又有了两条欧拉回路,输出 Yes
。
- 如果 ,则在上一种情况 的基础上,我们一定会多出若干个新环,也就是说此时我们的欧拉回路个数至少是 ,其中 为 中最少的欧拉回路个数。因此这种情况一定可以分为三个欧拉回路,必然会输出
Yes
。
形式化题意就是给你一个 的矩阵,元素的值域为 ,我们可以对每一行的元素进行重排,然后请你构造出一种重排后的局面,满足对于每一个元素 , 在每一列中的出现次数的极差不超过 。保证 是 的整数次幂。
先考虑 的情况。对于一个数 ,我们给它定义两种属性 表示 在第 列和第 列分别出现的次数。每次将某一行的 两个数重排成 之后,有 和 。这等价于建一条有向边 ,然后我们将其反向为 之后, 以及 。
因此我们就建 条无向边,每条边连接 和 ,我们需要给这 条边定向,最后要使得每个点 的 。如果每个点在无向图中的度数都恰好为偶数,那我们显然可以在图中找一个欧拉回路,然后按照我们搜索的顺序给每条边定向,根据欧拉图的性质,最后我们每个点 一定有 ,满足要求。
而如果有若干个点的度数为奇数,我们可以考虑新建一个超级源点 ,让 与所有度数为奇数的点连无向边,这样连边之后,则包括 在内的所有点的度数都变成了偶数。然后我们就在新图上找一条欧拉回路并给边定向,最后排除掉所有与 相连的边,我们的每个点就都满足了 。
的情况就处理完了。由于 ,我们可以考虑分治。具体地,从 开始分治,设 当前我们分治到的区间,则我们现在要给每一行中的 区间内的元素进行重排构造。
设 。由于相同元素在每一列的出现次数的极差不超过 ,因此不难想到一定存在一种合法的构造方案,使得 , 这个矩阵内 的出现次数 与 这个矩阵内 的出现次数 满足 。这个和我们 时要满足的条件是相同的。
因此我们可以对于一个位置 ,连一条到 的无向边,其中 。这个时候我们再采用 的计算方法,新建超级源点 建边,找一条欧拉回路并给边定向,这样我们就能使得各个元素在两部分的出现次数之差不超过 ,即两个部分就相互独立了,于是我们就能继续递归到 和 进行进一步处理,直到当前区间 满足 。
时间复杂度 。与这道题的构造思路类似的还有 [COCI2018-2019#1] Teoretičar,这里不再展开讲了。
不难得到一个思路,就是对于两个拼图 ,如果 能够左右相接,就在 之间连边,最后就是判断所有点能否连接成若干条链。
但是暴力建边是 的,我们考虑根据 这个变量优化一下,因为它的范围很小。思考一下,我们判断两个拼图能拼在一起就是根据它们外形特征上匹配,如果我们能构造一个类似哈希函数的特征函数 ,使得我们可以用一个关于 的代数式去判断拼图是否匹配,那就可以将问题简单化。在这里对于一个拼图 ,我们定义两个特征值 ,有:
则对于两个拼图 ,如果 ,则 可以接在 的右边。我们朝图论的方向进一步转换,可以发现等号的转换可以构成一张图,因此我们可以让 ,然后条件就变成了 。考虑对每个拼图 建立一条 的有向边,此时拼图就相当于一条边,值域上的每一个数就变成了点。
因此,对于一个拼图序列 ,如果我们可以顺次把 中的所有拼图拼在一起,则我们就一定可以在图上找到一条包含这些拼图对应的边的有向路径。于是问题就变成了能否在 DAG 上找到经过所有边的路径,且保证起点为一个负权,终点为一个正权。
根据半欧拉图判定,设 ,我们知道一个 DAG 存在一条欧拉路径当且仅当至多一个顶点的 等于 ,至多一个顶点的 等于 ,其余点的 等于 。根据基础的半欧拉图性质可以知道 等于 的必然是起点,等于 的必然是终点,而我们本题还要使得起点为负权,终点为正权,因此我们还要加上这两个条件。
此外,我们知道一个 DAG 中若所有点的 都等于 ,那么整张图就是欧拉图而不是半欧拉图,因此我们选出的欧拉路径全部都是欧拉回路,这显然是不合法的。因此我们还需要判掉这个情况。
最短路算法与负环
SPFA 与负环
在最短路中,对于一条边 我们定义一次松弛操作为 的更新操作。在一条最短路中,我们经过的点数最多为 ,因此对应的这条路径的边数最多为 ,于是我们只需要对每条边执行 次松弛操作就行了,时间复杂度为 。
上述算法为 算法。我们发现在这个算法中存在许多不必要的松弛操作,因此我们可以使用队列优化上述过程,即对于所有上一次被松弛到的节点 ,我们找到与 相连的边 ,然后只对这些边进行松弛操作。这样就能防止一些无用边被多次松弛到。
用队列优化后的算法叫做 ,其最劣复杂度仍然为 ,但是大多数时候跑得还是比较快的。
值得注意的一点是,如果图中出现了一个环 满足 ,那么只要我们反复走这一个环,我们的路径长度就会一直变小到 。因此有的题目我还需要判断图中是否存在负环。不过这处理起来也很简单,因为我们的松弛次数最多为 ,我们只需要在队列里的每一个元素里面都加一个当前松弛次数。如果当前松弛次数达到了 ,就可以将其判定为含有负环的图。
求一个分数的极值,显然考虑分数规划。我们二分一个答案 ,推出 这个答案能够被达到的判定式:
因此我们只需要令所有的边权 ,然后尝试在原图上找到一个环,使得这个环上的边权之和 。由于是二分一个小数,因此我们在保证精度误差的同时将 换作 ,那么现在我们就是要判断图中是否存在一个负环。
我们只需要从任意一点出发,在图上跑一遍 ,若过程中出现负环,那么就会使得当前松弛次数变为 。因此我们关注的是过程中的最大松弛次数,只要变为了 就可以判定 的可行性。
差分约束
差分约束是 算法的一种应用。
差分约束主要是这样一类问题:有一个 元一次不等式组,每条不等式形如 或者 ,需要构造出一组合法的整数解或者判断为无解。
我们先将后面的 转换为 ,这样就能使得每条不等式的结构统一。我们将 再转换为 ,然后就能发现这个形式和单源最短路中的三角形不等式非常相似,因此我们考虑把每一个 视作一个节点,把每个不等式 视作一条从 连向 ,边权为 的有向边。最后我们选定一个源点 ,从 开始执行一次最短路的 ,那么最终得到的 就是 的一种解。
特别地,如果 的过程中遇到了负环,那么我们就应该判定为不等式组无解。
如果你的不等式转换为了 ,那么就是求最长路。
如果你已经通过了 【模板】差分约束 这道模板题,那你就可以开始下面的学习了。
简要题意:给出一个不等式组,每组不等式形如 或 ,然后告诉了你其中若干个 的值,求一个最大的 使得这个不等式组无解。
不难想到二分 ,我们要做的就是判断 这个数是否会使得不等式组无解。
根据他给的不等式的形式,我们可以视作是差分约束中的加减号变成了乘除号。于是我们自然能够想到乘除法转加减法的一个经典方法:对数。我们将用不等式组改为 或 ,然后这道题就有变成了经典的差分约束题目。
而题目中还限定了某些 的值,我们可以另外造一个 并让其值为 ,每次的限制就是 。最后我们用一个超级源点 连向 的所有数,权值均为 ,这样就能保证 这一项必然为 ,且其余点一定都会被 处理到。
跑一遍最长路判负环就行了。
显然有些点我们是不需要考虑的,具体地,我们分别从 开始走一遍正图,从 开始走一遍反图,两次 DFS 的过程中都被遍历的点就是我们需要考虑的点,否则就一定不会对答案的构造产生影响。
在这一部分需要被考虑的子图中,我们要使得每一条 的路径长度相同,这可以等价于 ,每一条 的路径长度相等,如果有两条 的路径长度不同,那它们再走同一条 的路径就会不满足条件。
设 表示 的每一条路径的长度都是 。则对于每一条边连向 的边 ,我们需要找到一个边权 使得 ,只有这样才能保证 是唯一的 路径长度。即设 为这些能够连向 的点,有:
因为边权只能是 或 ,因此我们移项得:
由此我们可以使用差分约束去求出一组解,对于每一条有向边 ,我们再另一张图上建有向边 ,边权为 ,以及有向边 ,边权为 。在新图上执行 的时候,通过找负环来判断有无解。
最后输出每条边 的边权时,我们只需要输出 的值即可。而对于没有涉及到的边(即 中至少存在一个不需要被考虑的节点),我们就直接随机赋权然后输出。
强连通分量
依赖偏序关系的有向图建模
偏序分为非严格偏序和严格偏序,它们之间的差异就像是 和 的区别,具体地,设 为全集 中的一个二元关系,若 满足自反性(,有 ),反对称性(,有 ),传递性(,有 ),则 被称为全集 上的一个非严格偏序关系。而若 不满足自反性,且满足传递性与非对称性(不存在 使得 ),那么 就是一个严格偏序关系。
在一个抽象的问题中,我们有时会利用偏序关系进行图论建模,让问题变得更加直观。比如我们可以拿 作为建图用的偏序关系,然后对已知关系 进行建边,通常我们会使用有向边 表示 。
一般我们用到的偏序关系为大小关系,区间包含关系,字典序关系,整除关系,子集关系等等。但是我们更常用的应该是大小关系这一类。如果一个题目给出了若干个偏序关系的限制,我们就可以考虑图论建模,但这不一定是对的,不要看到偏序关系就往死里冲图论。
当我们根据偏序关系建图之后,若是非严格偏序关系,那么就是一个普通有向图,若是严格偏序关系,那么就是一个有向无环图 DAG,因为一旦出现了环就会违反自反性以及非对称性。因此在根据严格偏序关系建图时,我们就可以通过判环的方式去解决有无解问题。此外我们还有一个结论,就是当我们依赖非严格偏序关系建图时,对于一个环 ,根据传递性和自反性可以知道这个环上的节点权值相同,就比如我们用 这个符号去建有向边时,如果出现了一个环,那么这个环上所有点只能取等。
在有向图上找环的话我们可以直接用 求强连通分量,如果当前强连通分量的大小大于 ,那么这里面就存在至少一个回路。
如果你明白了上面讲的,那下面这道例题应该也不会太难了。
贪心地想,在保证合法的情况下,我们肯定是希望每一组限制都尽可能在靠前的位置满足,即对于一组限制 ,我们希望最小化一个值 使得 的字典序小于 ,因为这样的话我们就有更多的位置可以自由发挥。
先特判掉一种情况,若出现 的限制,我们就只需要保证 就行了。而对于剩下的情况,我们可以利用上面的一个贪心思路,设 表示第 个限制最早可以在第 个字符比较出 。先思考每一个 是否可以都等于 ,即判断能否让所有的 ,因此我们将 作为建图使用的偏序关系,初始时我们就直接从 连到 以体现这种偏序关系。接下来就有两种情况:
- 如果有向图中无环,那么就一定存在一种方案,输出
Yes
。
- 如果有向图中有环,根据非严格偏序的自反性和传递性,我们只能让这个环上的每个点对应的数全部相同。如果有一个限制 的边 出现在了这个环上,那么就只能让 以及 ,然后去检查这个限制的下一位能否满足条件。而如果没有出现,我们就暂时视作 。
这是针对 的初始情况。由此,我们可以得到一个思路:每次都连一条有向边 表示 的限制,然后在图中用 找强连通分量,如果存在大小大于 的分量,则强制让这个分量中的 ,令 以等待下一轮操作,然后把这个强连通分量缩点,表示这一部分的 全部相同。如果不存在,那么就是直接输出 Yes
。
由于图中存在强连通分量缩成的点,因此我们每次连边都是去找到它所在的强连通分量,然后再去连边。
如果途中发现 ,则表示 被强制为了 的一段前缀,那么当前一定无解。
我们可以用并查集去维护每个点所在的前强连通分量对应的缩点,每个 最多会变化 次,每次 是 的,因此复杂度为 。
2-SAT
2-SAT 问题就是给你 个布尔变量,现在给出若干个限制 ( 且 )表示不能出现 这样的情况,然后判断是否存在合法的赋值方案。
考虑把每个变量 的两种取值分别设为点, 表示这个变量取 , 表示这个变量取 ,对于每条限制,我们建有向边 和 。什么意思呢?通俗来讲,我们建的每一条有向边 表示若变量 取了 这个值,那么变量 就只能取 这个值。由于每条限制规定不能同时出现 和 ,因此当 时我们的 就一定不能取 ,因此有 。 同理。
在这个有向图上我们可以发现传递性,即根据 和 这两条边我们可以推断出,若变量 取 那么必然有变量 取 。由此可以得出,若有向图上有 ,则变量 取 时必然有变量 取 。
我们考虑一个同时包含了 和 的回路,在这个环上显然有 ,这就说明 不管取 还是 都会出现矛盾,这就导致了无解。因此我们在有向图上执行一遍 算法,将每个强连通分量缩点,如果存在一个 使得 被缩在了同一个点,则答案无解。
那在有解条件下,我们该怎么输出一种给每个变量赋值的方案呢?既然保证有解,此时我们就只需要考虑 这种单向的可达性,不难发现这种情况中 的拓扑序必然是小于 的,因此我们考虑变量 的值时,直接输出拓扑序更大的 中的 就行了。由于我们 求出来的 编号是反拓扑序,因此我们可以不用重新拓扑一遍,输出 中 编号较小的一个即可。
为了使问题更直观,我们可以把题目给你的哈密顿路径提出来,并摆成一个环。那么其余边就相当于是取环上的两点,然后在两者之间连边。
考虑不在环上的两条边 ,我们现在这个环内连接 ,如果 出现了交点,那么我们就可以任选其中一个,并在环的外面对其进行连边,具体如下图(在环内连边时, 会相交,因此我们可以把 放到外层连边):

我们可以对每条不在环上的边定义两种状态 ,分别表示这条边在环内部连,以及这条边在环的外部连。用 表示第 条边的状态,若两条边 在内部会相交,则必然有 ,因为两者不可能同时在内部连,或者同时在外部连。
由此我们就可以构建 2-SAT 模型了。对与两条会相交的边 ,如果 ,那么 必然选 ,如果 ,那么 必然选 。这个按照模板的格式建边,然后找一下图中是否存在强连通分量来判无解。
注意到边数可能会很多,我们可以直接用平面图的边数上限 来特判掉边数极大的情况。
经典的 2-SAT 求方案数问题。
首先我们可以简单地构建一个 2-SAT 模型。对于两个相识的人 ,如果 是同谋者,那么 必然在后勤组,如果 是同谋者,那么 必然在后勤组,这是在规定同谋者两两之间不认识。而对于两个不相识的人 ,如果 在后勤组,那么 必然为同谋者,如果 在后勤组,那么 必然为同谋者,这是在规定后勤组的人两两之间相识。
因此我们设 点表示第 个人为同谋者, 点表示第 个人为后勤组,有 。若 相识,则连边 ;若 不相识,则连边 。
然后求一遍强连通分量,我们就能判断有无解了。
如果要求方案数,我们可以先随机处理出一个可行方案。具体地,设 表示 所在的 编号,若 ,则 点的拓扑序小于 点的拓扑序,此时我们就让 为同谋者,否则为后勤组。设 为同谋者编号集合, 为后勤组的人的编号集合。
考虑一个性质:设另一个可行方案的两个集合为 ,有 和 ,即其它方案中,我们最多只会将 中的一个人放到 ,并且最多只会将 中的一个人放到 ,如果是两个的话,就会出现同谋者存在认识的人,或者后勤组存在不认识的人的情况。
因此我们就讨论一下三种情况:
-
将 中的一个人放到 。我们枚举 中的一个元素 ,将其放在 中,我们必须满足 与 中的任何元素存在连边, 判断即可。
-
将 中的一个人放到 。我们枚举 中的一个元素 ,将其放在 中,我们必须满足 在 中的任何元素都不存在连边, 判断即可。
-
在 中各选一个元素 和 进行交换。我们只需要考虑 中除了 的元素是否都与 连边,或者 中除去 的元素是否都与 不存在连边。对于 ,我们只需要找出 在 的连边数量 ,如果 不存在连边就让 ,若 则 可以放过去。对于 同理。时间复杂度为 。
过程中一定要注意 随时要 。
总时间复杂度依旧为 。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现