冯梓轩图论总结2

图论学习总结 2

拓补排序

当给定的一张图是一张 DAG(有向无环图)时,可以对该图进行拓补排序,在 O(n+m) 的时间内转移一些信息。通常用队列进行实现。拓补排序经常与其他算法进行结合,如 DP。

例题

[POI2015] PUS (Pustynia)

当一些数之间只给定了大小关系,要求一组可行解时,可以考虑使用拓补排序求解。考虑用较小的数往较大的数连边,由于我们想让一个数在满足限制的条件下尽可能的小,所以它的值可以定为比它小的数中的最大值加 1。若存在 ai<aj<ak<<ai,显然无解,而此时这些数转换到图上构成一个环,因此只要图中有环就无解。对于开始已经给定值的数,让其点权先等于给定值,若跑完拓补排序后与原值不同也无解。

如果存在值相同的情况,可以把相同的值放到一个并查集里,看作一个点,统一限制。

【HNOI2015】落忆枫音

先考虑没有环的情况情况怎么做,答案显然为 i=2ndi,其中 di 为节点 i 的入度,相当于给每个节点(1 除外)钦定一个父节点。

现在考虑有环的情况。由于正着不好做。所以考虑还是用原来的方式求。设加入新边之后点 i 的入度为 di,则当前算出的答案为 i=2ndi。但是由于会把环的方案算进去,所以考虑把包含环的方案减去。本来我以为添加一条边最多产生一个环,然而有很显然的反例。

不加边之前:

加入边 (2,3) 后:

这明显已经产生了多个环,所以不能考虑将环一个个找出。由于原图是 DAG,所以不管在原图上选出哪些边都不会形成环,因此增加的环在删去 (s,t) 这条边后一定是一条 ts 的一条路径,只需统计 ts 的路径方案数即可。考虑使用动态规划,设 fi 表示在原图上从 ti 的路径方案数,方程即为:

fv=uvfudv

相当于原来可能有多个父亲,但是我只钦定一个。最后把按照原来方式算出的方案数减去 fs 即可。

[CSP-S 2020]函数调用

本来我想的是把对一个函数的调用堆积到最后一起计算,但是搞了半天也不会。由于乘法操作是一次把所有数都乘以一个系数,所以最后可以对原值直接操作。现在的棘手问题是加法操作的对象是唯一的,所以加权的系数需要分别计算。由于先前的加法操作在给其对应位置加值后,后续的乘法操作会将其加权的系数扩倍,所以可以将其先后顺序颠倒一下,方便计算。设 fi 表示调用函数 i 会使其后继加法函数的系数,方程即为:

fv=uvfup

这里的 p 是比 v 的优先级低的函数对乘法操作的系数贡献累乘,而对于该函数内的乘法系数贡献可以进了下一个函数再细分。对于最后的一堆函数调用,我偷了个懒,直接新建了一个超级源点,按顺序连向这些函数,并使 fs=1,这样可以避免一些冗余的操作。

二分图

二分图的判定

染色法,将与自己相邻的点染成不同颜色,如果矛盾则不是二分图。

例题

双栈排序

看到“双”栈,而且还看到要判定无解,很容易想到用染色法判定二分图。考虑如何建模。

根据二分图染色的过程,相邻的点与自己颜色不同,所以图中的边连接了两个属于不同栈的节点。考虑 i,j(i<j) 在何时才必须不在同一栈内。显然只有这两个数是不行的,因为只看这两个数是一定有方案使它们在同一个栈的。只有当 k>jak<ai<aj 时,ij 才必须不在同一栈内,因为由于大小关系 i 必须在 j 前出栈,而位置关系又决定了 ki 之后出栈,这样就是降序排列,不符合。这就是染色法判定二分图。

在染色后就可以得到每个元素归属于哪个栈。由于要求字典序最小,所以考虑贪心,在入栈 1 时先弹栈 1,在必须弹栈 2 时再弹,之后入栈。在入栈 2 时,先把栈 1 能弹的都弹了,再弹栈 2,最后入栈。

欧拉路径

此知识点最难的就是:看出一道题与欧拉路径相关;有欧拉路径进行建模。

例题

POLICE

思路太妙了,实在是佩服能想出来的人。

先考虑所有书都在原书架的情况。由于要尽可能的少用操作 2,就要多用操作 1。在保证一定有空位的情况下,我们可以用操作 1 完成多次挪动。可以将原书架上的书位置与现书架上的书位置做一个映射,容易发现满足了位置靠后的映射就不能再满足靠前的映射了。所以不用操作 2 的最大操作 1 次数即为映射后的 LIS 长度,需要的操作 2 次数就是书的本数减去 LIS 的长度。

接下来考虑一般情况。对于在其原来书架上的书,我们还是按上述方法,这样可以减少操作 2 的使用。对于必须与其它行的书交换的,我们把一次交换抽象成一条有向边。显然,我们至少需要使用边数次操作 2 才能使得所有书归位,但实际上我们也 仅需使用边数次操作 2。考虑选出一个入度大于出度的点连向一个出度大于入度的点,一直连边,直到所有点的入度都等于出度。可以发现,连完边的图 __存在欧拉回路,在欧拉回路上,每条边都恰好对应了一次交换,而且这些交换可以通过合理安排顺序使得所有书归位。__这样就证明了只需使用边数次操作 2。需要注意的是,如果当前联通块中如果没有空位的话,我们还需要一次额外操作,将空位交换到这个联通块中。

这道题确实很难想到与欧拉回路有关。

宝石装箱

可以发现把宝石或着价值作为点,都无法很好的处理限制,所以考虑以颜色作为点,把在相同箱子的连一条边。

先满足第一个条件,发现和等差数列很类似,直接用宝石 i 的颜色向宝石 4ni+1 的颜色连边,这样保证了所有的边上端点点权和为 4n+1。之后显然可以发现,图上所有点的度都为 4,所以必定存在欧拉回路。把欧拉回路跑出来后,我们只需要 间隔的选边,就可以满足第二个限制,因为每条边向两个端点都贡献了一个度。

[省选联考 2020 B 卷] 丁香之路

发现其实就是求一条 si 的欧拉通路,只不过是每条边至少经过一次。

我们先把 m 条边加入答案边集中,方便处理。由于欧拉通路不太好处理,考虑转换成欧拉回路,由于 si 是端点,所以将 si 连边即可。现在考虑将边集变为欧拉回路,实质上就是将奇度数的点相互连边。考虑将相邻的奇度数点连边,这样可以保证最优,因为相邻点连边一定优于跳跃连边。但是还有一个问题就是 图不一定连通。由于联通块内已经连通,所以只需要考虑在联通块之间连边。还是套用刚刚的方法,将相邻且不在同一联通块内的点连边,依旧可以保证最优,因为跳跃连边不优。可以发现,用最小的代价将图联通就是最小生成树,所以跑一个 Kruskal 即可。注意,将图联通时,由于要保证所有点的度数为偶,所以要连两条边。

最小生成树

最小生成树常与其他知识点结合,如倍增、树剖、线段树等。

例题

跨越雷区

发现左右边界没有什么用。显然,答案具有二分性,因为大的答案都满足了,小的也能满足,所以考虑二分答案。每个点所管辖的区域都是一个以其为圆心,半径为二分值的一个圆,如果两个圆有交(圆心距离小于二倍半径),则它们中间的区域不能通过,我们可以用一个并查集维护,将这两个点所在并查集合并,再建两个虚点,分别代表上界与下界,再用相似方法连边,最后若上界和下界联通,则说明路被堵死,无法通过。但是由于数据过于毒瘤,无法通过。

考虑二分的过程,发现实际上就是逐渐尝试扩大半径,等到上下界联通后就停止。这其实就是最小生成树,将其跑出后,最小生成树上的最大边就是满足条件的最大圆心距离,将其除以 2 就是答案。

但是如果用 Kruskal 求解的话,时间复杂度还是带一个 log,依旧超时。考虑到图是一张完全图,所以可以用 Prim 算法求解,时间复杂度 O(k2),可以通过。

水壶

考虑贪心的思想:能走建筑物就走建筑物,这样可以保证最大值最小。

先跑一遍多源最短路,并记录最短路从哪个点转移过来。如果两条最短路相遇,且两条路径起点不同,就将这两个路径起点相连,这样可以保证两个点之间的最大边权相对较小。然后再用 Kruskal 重构树,将环上不必要的大边去掉,最后答案即为 uv 的路径上的最大边权。

强连通分量(SCC)

主要技术就是缩点,缩点之后是一张 DAG,可以在上面跑拓补,然后和 DP 等算法配合。

例题

狼人游戏

可以发现,在一个 SCC 中,只要问一个点,就可以知道所有点的情况。所以先把图缩点,然后发现知道了一个 SCC 的情况后,它的后继节点也可以知道了,所以只有入度为 0 的 SCC 是需要进行询问的,做一个统计。但是这样有一个问题,假设有四个人,我已经知道了 1,2,3 都不是狼人,那 4 就一定是狼人。所以要特别考虑一种情况:如果一个 SCC 中只有一个点,且它的后继节点不一定非要它才能被了解(即入度大于 1),那么就可以不用询问它。注意,如果有多个这样的点,最多只能不问其中一个。

最后的答案计算,假设总共需要问 k 次,问第 1 次存活下来的概率为 n1n。问第二次时,虽然有些点已经知道,可以不用再问,但是我们也可以去问,反正存活率 100%,不影响,所以问第 2 次存活下来的概率为 n2n1,第三次为 n3n2,第 k 次为 nknk+1。最后答案即为:

n1nn2n1nknk+1=nkn

交通网络

本来以为能访问的区间不是一段连续的,那这么做就会超时。但是由于 图是一张平面图,所以有一个很重要的性质:一个左部点能到达的右部点按照 y 坐标排序后一定是一段连续区间(除开不能被任何左部点到达的右部点)。这样就可以直接先缩点(一个 SCC 中的点可以相互到达),然后通过拓补合并一个点能到达的最小左端点和最大右端点,最后求出该区间内有多少能被左部点到达的右部点即可,可以使用前缀和。

双连通分量(DCC)

双连通分量有一个很好的性质:缩点之后是一棵树。边双的缩点与强连通分量类似,这里主要介绍点双缩点:Block-Cut Tree。

Block-Cut Tree

由于一个割点可能存在于多个点双中,所以不能简单的把点双缩成一个点。但是我们可以考虑 把点双中不是割点的点缩成一个点,然后再和点双中的割点连边

如下图,原图为:

进行缩点(每个点双中,非割点的点缩点后选择一个原来的点编号作为新编号)后:

这样,图就变成了一棵树,可以进行一些方便的操作。

例题

[AHOI2005] LANE 航线规划

正着做显然不好做,考虑向并查集一样,把正删边改为倒加边。由于边双内的边和点都不会产生贡献,所以将边双缩点,之后就能得到一棵树。考虑加边,树上在加了一条边后,会且仅会产生一个环,而环上的边(即在两点路径上的边)都不会再是割边,贡献从 1 变为 0。相当于我们要进行链修改,链查询,可以考虑用树剖维护,时间复杂度为 O(nlog2n)。也可以考虑用树状数组加并查集维护,时间复杂度为 O(nlogn),更优秀。

「CEOI2017」One-Way Streets

一个很显然的性质:边双中的边不用定向,因为边双中两点间有至少两条边不重复的路径,所以有多条可行路径,没有必须经过的边。所以将边双缩点,得到一棵树。从树根开始对树进行遍历,实质上就已经根据点的访问顺序将树边定向。接下来,考虑将从 uv 路径上经过的割边定向,先求出 u,v 分别所在的边双编号 u,v,结合跳 LCA 的过程,从 uLCA 上的树边与遍历树时的方向相反,而从 LCAv 上的树边与遍历树时的方向一致,考虑用倍增进行打标记即可。

小结

图论,实际上最难的一般不是在图上求解问题,而是 找出题目与图的关系,将元素之间的关系抽象成图,将原问题转化为图上的等价问题进行求解。所以,多数图论题难就难在建模,如欧拉路径相关题目,需要勤加练习才能总结出一些经验,同时也需要灵活的思维才能找到联系。

posted @   gevenfeng  阅读(52)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示