知识学报:图论(1)

不是题解不是教学!!!

11.10

CSES 1192

给定一个 \(01\) 网格图,求 \(0\) 的连通块个数。
遍历整个图,当遇到 \(0\) 时搜索并把联通的全部变为 \(1\),连通块数量加一。

CSES 1193

给定一个 \(01\) 网格图,问是否可以仅经过 \(0\)\(A\) 点到 \(B\) 点,如果可以输出路径。
从起点 bfs,每个点记录上一步从哪里来,如果走到终点则倒推整条路径再反向输出。

CSES 1666

给定一张无向图,不保证联通。问添加几条边可以让整个图联通,并输出添加的边。
先并查集,遍历所有点,如果某个点与点 \(1\) 不在同一个并查集就联通他和点 \(1\)

CSES 1667

给定一张有向图,求不带权最短路从 \(1\)\(n\)
bfs 即可。

CSES 1668

给定一张图,要求给点涂色,使相连的点颜色不同。
初始不涂色,遍历所有点,如果一个点没涂色就跑 bfs 给所有相邻点涂相反颜色,如果冲突则不可能涂色。

CSES 1669

给定一张无向图,求一个环。
遍历所有点,如果一个点没被经过就跑 dfs,dfs 中如果出现重复就记录并回滚到重复点处并倒序输出。

CSES 1194

给定一个网格图,其中有空地和墙和怪和你,你和怪同时走路,问能否设计一条路走到网格图边界。
以所有怪为起点跑多源 bfs,然后以你为起点跑 bfs,你只能走到 bfs 的值比怪的 bfs 小的点上。

CSES 1671

无负权边单源最短路。
dij。注意 dij 中如果当前取到的点的 dis 比保存的 dis 大则跳过,否则可能退化 \(O(n \times m)\)

CSES 1672

求所有点到任意点的最短路。
floyd。设计一个 dp,\(dis[k][i][j] = \min(dis[k][i][j],dis[k - 1][i][k] + dis[k - 1][k][j])\),意为只使用编号前 \(k\) 个点从 \(i\)\(j\) 的最短路,转移是看 \(i\)\(j\) 中要么经过第 \(k\) 个点要么不经过。编码时可以省略第一维节省空间。注意要初始化 \(dis[i][i]=0\)
floyd 也可以用于含负边权图,如果 \(dis[i][i] < 0\) 则代表含负环。

CSES 1673

有向图求 \(1\)\(n\) 最长路。如果最长路无限长输出 \(-1\)。含负边权。
spfa。注意每个点在队列中只入队一个,如果已经入队那么只松弛边而不入队。当一个点松弛超过 \(n\) 次时含正权环,因为一个不重复的路径最多经过 \(n\) 个点松弛 \(n\) 次。
交上去 wa 掉。因为有向图发现正权环直接输出 \(-1\),然而这个正权环里的点可能无法通向点 \(n\),对答案没有影响。所以要先建一个返图,确定哪些点可以到 \(n\) 点,spfa 时遇到到不了 \(n\) 点的点就直接跳过。

CSES 1195

无负边权单源最短路,有一次把一条边边权减半的机会。
分层图最短路跑 dij。

CSES 1197

有向图找一个负环并输出。
spfa,记录每个点被松弛时的路径的前一个点,当找到一个松弛 \(n\) 次的点时,往前找 \(n\) 个父亲后必定在环内。然后在环内不断往前找父亲直到找到自己为止。

CSES 1196

\(k\) 短路。可以重复经过点。
dij 变种。对于每个点记录起点到该点前 \(k\) 短的路,当已经保存了 \(k\) 条路,也就是经过这个点 \(k\) 次后舍弃剩下到这个点的路。因为用优先队列保存,所以每次进入这个点时都是除了前面的路外最短的,所以前 \(k\) 个就是答案。对于这个点 \(k\) 个之后的路径,对其他点也不可能有贡献,所以直接跳过。时间复杂度为 \(O(km\log km)\)

CSES 1678

找一个有向环。
dfs,把正在查询的点的 \(vis\) 改为 \(1\),已经遍历完它的所有子树的点的 \(vis\) 变为 \(2\),当遇见一个 \(vis\)\(1\) 的点时代表有环,此时倒序找回去即可。遇到一个 \(vis\)\(2\) 的证明下面没有环,可以直接退回。

CSES 1679

一个有向图,只能出入度为 \(0\) 的点,求一个顺序或输出无解。
拓扑排序即可。

CSES 1680

一个有向无环图,求点 \(1\) 到点 \(n\) 的最长路径。
拓扑排序,当一个点到另一个点时,如果路径更长就更新这个点的路径长度和父亲,找到最后一个点后根据父亲倒推找回。注意非 \(1\) 的入度为 \(0\) 的点一开始也要入队,但长度为负无穷。

CSES 1681

一个有向无环图,求点 \(1\) 到点 \(n\) 方案数。
类似上一题拓扑排序即可,非 \(1\) 的起始点贡献方案数为 \(0\)

CSES 1202

求点 \(1\) 到点 \(n\) 的最短路长度,最短路方案数,最短路经过最少和最多节点数。
带状态的 dij。当 dij 检查是否松弛时,如果松弛成功则把原来的全部信息覆盖掉,如果和原长度相等则只更新状态,但不入队。

CSES 1750

\(n \leq 1e5\) 个节点,每个节点有一个出度,\(q \leq 1e5\) 个询问,节点 \(i\)\(k\) 步到哪里。
倍增优化。求出 \(2\) 的次幂步点 \(i\) 会走到哪里,可以在 \(\log\) 复杂度内回答询问。注意不能在询问中倍增所有 \(n\) 个节点的目标,因为一次计算的复杂度为 \(O(n)\)

CSES 1160

类似上面的情况,问题变为节点 \(i\) 走几步能到节点 \(j\) 或不可能走到。
注意到是一个基环树,考虑基环树 dp。先找出所有环及环上节点,然后以环上每个节点为根做 dfs 并在这个子树上预处理倍增。判断两个节点能否达到时,不在同一个环上不可能达到。在同一个环上,\(j\) 如果在环上则必定能走到。否则必须在同一颗子树上且 \(j\)\(i\) 的祖先。
环上路程为 \(j\) 在环上的位置减去 \(i\) 的树根的位置再加环的大小后模环的大小。注意如果是倒序存储的环的位置应为 \(i\) 的位置减去 \(j\) 的位置。

CSES 1751

类似上一题,求所有位置走多少步会走到一个重复的点上。
解决方式和上题完全一样,把倍增的部分换成 dfs 加一个原位置的环的大小即可。

CSES 1675

最小生成树。
并查集维护,从最小边开始尝试是否有需要加,即 kruskal。

CSES 1676

一开始没有边,每次添加一条边,求连通分量个数和最大连通分量。
并查集,每合并一次连通分量减一,两个连通分量大小加一起。

CSES 1682

问一个有向图是否所有点之间互相可达,即整个图是否是一个强连通分量。
发现如果所有点都能到一个点,且这个点可以到所有点则满足条件。且如果所有点间互相可达,则任意点都满足刚才那个条件。于是对节点 \(1\) 在原图和反图上各做一次 dfs 即可。

CSES 1683

问一个有向图中有几个强连通分量,且输出哪些点在同一个强连通分量。
类似刚刚题目的思想,如果有一个点可以到剩下所有点,那么剩下的点中可以到它的点都和它在同一个强连通分量里。考虑记录后序遍历的 dfs 序并放入栈中,那么栈顶可以到此时栈内它在原图中可到的所有点,而栈内的它在原图中不可到的点如果有到栈顶的路径,则栈顶这个点肯定会出现在那个点的下方而非栈顶,所以栈顶的反图不可能到达栈内的原图不能到达它的点。于是取出栈顶跑反图,遍历到的点和栈顶在同一个强连通分量,然后把它们取出。直到栈空为止。即 kosaraju 算法。

11.11

CSES 1684

\(n\) 个点,\(m\) 条限制。每条限制给出两个条件,至少其中一个条件满足。条件的形式为选择或不能选择某个点。
这种要求两个条件满足其一,且条件为 bool 型的就是 2-sat 问题。可以把不选择点 \(i\) 用一个新点 \(i + n\) 表示,也就是点 \(i\) 和点 \(i + n\) 不能同时选择。这样就可以把条件中不能选择点 \(i\) 转化成要选择点 \(i + n\)。根据限制,如果一个条件无法达成,那么另一个必定达成,可以转化为从条件一要选择点的对立面连一条边到条件二要选择的点。意味着如果选了第一个条件的对立面,则必定要满足条件二。
建完图后,跑一个 kosaraju 求出所有强连通分量。对于一个强连通分量,意味着选了其中一个点其他所有点都必定要选,如果点 \(i\) 和点 \(i + n\) 在同一个强连通分量中则无法满足所有限制。如果可以满足所有限制,则对于某个点选择强连通分量序号较大的那个,因为强连通分量的序号是在反图上取的,序号较大强连通分量必定没有路径在原图上到达序号小的强连通分量,所以选择序号大的那个点必然不可能导致另一个对立点被选择,而如果选择序号较小的那个则有可能有路径在原图指向它的对立点导致违反。

CSES 1686

给一个有向图,每个节点有权值,可以选择任意起点和终点,求经过的所有点的权值和最大为多少。
容易想到把环缩成一个点,环中权值一定都可以全取,且可以从任意环上点进入并从任意环上点离开。进而想到强连通分量就可以达到这个要求。于是求出所有强连通分量,统计每个强连通分量的权值,以及遍历所有边,如果两个点不在同一个强连通分量则给它们所在的强连通分量连边。强连通分量的图是一个有向无环图,用拓扑排序上 dp 即可。

CSES 1691

无向图上求欧拉回路。
所有点度数为偶数存在欧拉回路。dfs,记录每个点当前用了前几条边,记录无向图分开的两条有向边为同一个 id,使用一条边后这个 id 再遇到就跳过,再进这个点时从下一条边开始。在此基础上,后序遍历记录节点顺序即可。

CSES 1692

给定一个 \(n \leq 15\),要求构造一个最短的 \(01\) 串使它存在所有长度为 \(n\)\(01\) 子串。
把长度为 \(n-1\)\(01\) 串设为点,长度为 \(n\) 的所有串为边,那么一个边的两端就是去掉头尾的两个点。因为一个点前后能加 \(0\)\(1\),所以所有点的度数都是偶数,要求有所有长度为 \(n\) 的子串就是构建一个图使它构成欧拉回路经过所有长度为 \(n\) 的串的边。从 \(0000\) 这个子串开始,可以在后面接 \(0\)\(1\),并标记这条边是否走过,像上题一样构造出欧拉回路后逆序输出即可。

CSES 1693

有向图上求欧拉路。
设入度为 \(1\) 出度为 \(-1\),度为 \(1\) 的点是终点,为 \(-1\) 的点是起点,其他点度数均为 \(0\)。dfs 方法和无向图一样,但不需要记录边的编号了。注意在进入新递归前就把计数器加一,否则会进入死循环。

CSES 1690

\(n \leq 20\),从点 \(1\) 开始,求每个点经过且只经过一次的方案数。
状压经过了哪些点,dp 即可。注意可以通过 \(i \& -i\) 快速得到只有最低位的 \(1\) 的数值,通过__builtin_ctzll(k) 得到 \(k\) 的数值是第几个 \(1\)。可以较大的优化常数。

CSES 1689

一个 \(8 \times 8\) 的网格,给定一个骑士的初始位置,构造一种跳跃方法使所有点恰经过一次并输出。
直接 dfs 会 TLE,加入优化优先跳向可跳格子最少的下一格。

11.12

CSES 1694

网络流。求最大流。
考虑如何朴素的得到正解。把容量为 \(0\) 的边视为不存在,给每条 \(u\)\(v\) 的边设一个相反的容量为 \(0\) 的从 \(v\)\(u\) 的反悔边。先跑一遍 dfs,记录当前的点和流量,通过边把当前流量和边的容量中较小的那个作为流量传给下一个点,直到到达汇点就是真正这条路径的流量。把所有经过边跑的容量减少这条路真正的流量,然后把这些边的反悔边容量增加这个流量,答案也增加这个流量。然后再跑 dfs。当走过一条反悔边时,相当于走过这条原边的一些流量退回去走当前路径,而当前路径的一些流量去跑刚刚那条路径的后半部分。由于每次 dfs 到汇点时都会增加答案,而到不了汇点时就是所有通路被填满,所以某一次 dfs 答案没更新就找到了最大流。
这样子的做法会遇到问题,由于是 dfs,可能随机的走到了一条很小的边,但正确走法并不走这条很小的边,而是走另一条很大的边到终点。那么接下来 dfs 的时候就会通过这条很小的边反悔回去,也就是这条小边成为限制,时间复杂度就变成了 \(O(m \times F)\),F 为最大流大小。
如果使用 bfs 找,也就是每次都找最短的路径。对于一个当前最短道路使用的容量最小的原边 \(u\)\(v\),当使用过后会增加一个反悔边 \(v\)\(u\),刚刚由于 \(v\) 距离源点的距离由 \(u\) 定义,所以此时 \(v\) 的距离只可能增大或不变。当一次从 \(v\)\(u\) 时,意味着 \(u\)\(v\) 的距离加一,那么 \(u\) 就变成原来的距离加一。同理再有一次 \(u\)\(v\) 作为限制时 \(v\) 的距离是原来加二。而一个点距离源点的距离是从 \(1\)\(n\),那么一条边最多 \(n\) 次作为限制,总共 \(m\) 条边,就会最多 \(m^2\) 次搜索。每次搜索是 \(O(m)\) 的,时间复杂度就优化成 \(O(n \times m^2)\)
但是这仍不足以通过。考虑刚刚的算法哪里可以优化。每次 bfs 保证路径长度最小,然后使用最小的路径长度去更新。然而一次更新可能并不会改变最小路径长度,我们可以把所有路径长度最短的路径通过一次 bfs 全部找出来,然后把这里所有的路径都处理一遍,然后再跑 bfs,这样 bfs 就只用跑 \(O(n)\) 次了。于是跑 bfs 并统计每个点距离源点的距离,然后跑 dfs,只有 \(v\) 恰等于 \(u\) 的距离加一的边才使用。使用类似找欧拉路的方法判断要使用的边,均摊下来复杂度是 \(O(n \times m)\) 的,总复杂度就变成 \(O(n^2 \times m)\),可以接受。
证明均摊复杂度。这个 dfs 可以分为两块:走在正确的道路和错误回退。每个边最多错误回退一次,复杂度 \(O( m)\)。走到正确的路上,长度最大为 \(n\),而每次走在正确的路会把一条瓶颈边删掉,最多有 \(m\) 条瓶颈边,所以总复杂度为 \(O(n \times m)\)
注意实现时一条边没有流光的话不能跳过,下一次 dfs 还要用。

CSES 1695

求最小割及其边。
是把图分成两个部分,一个部分中包含源点,另一个部分包含汇点,这两个部分中相连的边的容量之和就是割的容量。容易想到最大流小于等于最小割,因为流量最多填满这个割的所有边,所以总小于等于任意割。对于一个跑完最大流的图,把所有源点能到达的点作为第一部分,剩下的作为第二部分。从第一部分到第二部分的原边肯定没有剩余容量,否则源点可以到达;第二部分到第一部分的原边的反悔边同理也是 \(0\),所以原边肯定没有任何流量。那么净流量就是从第一部分到第二部分的原边的总容量,此时把第一部分视为源点,第二部分视为汇点,那么净流量就是总流量,即最大流。同时这也是一个割。再加上刚刚证明的最大流小于等于最小割,所以这就是最小割。
得到了最大流等于最小割。其中,所有从第一部分到第二部分原边剩余容量为 \(0\) 的边就是割边。
这道题目在无向图中,注意无向图构造网络流时是构造两条容量相同的原边并分别有两条自己的反悔边,而不是互为反悔边。

CSES 1696

求二分图最大匹配。
设计一个源点到所有左侧点有边,容量为 \(1\)。所有右侧点到一个汇点,容量为 \(1\)。跑最大流,最大流就是最大匹配数量。因为每一对匹配就相当于一个流量为 \(1\) 的流。

CSES 1711

求一个图中边不重复,从 \(1\)\(n\) 最大路径数量和路径。
把所有边视为容量为 \(1\),跑网络流,总流量就是最大路径数量。把所有剩余容量为 \(0\) 的边挑出来跑 dfs 并回溯路径即可得到所有具体路径。
同理,可以有点不重复路径。把一个点拆成两个点 \(in\)\(out\)\(in\)\(out\) 边容量为 \(1\),原来的边 \(u\)\(v\) 设为从 \(uout\)\(vin\) 的容量无穷大的边,同理跑最大流即可。

11.13

Luogu P3388

求割点。
对于一个图中,对它跑 dfs,其中它的子树如果有边可以到它的子树之外的点,那么这个点即便删掉也不会增加连通分量,故不是割点。也就是以这个点为根的子树上的所有点都不能到 dfs 序小于这个点的点,则这个点是割点。判断子树上的点能到哪里,当下一个节点未被遍历过时,放到它下面。如果以及遍历过,则更新这个点能到的最小 dfn。子节点的最小 dfn 可以向上传递到父亲节点处。
对于图的“根”,则是如果有两个或更多未被 dfs 的点与根相连,则这个点是割点。

Luogu P1656

求割边。
类似上面,当一条边的终点 \(v\) 可以直接连到 \(u\)\(u\) 之前的点时就不是割边,也就是当 \(low[v] > dfn[u]\) 时这条边是割边。

posted @ 2025-11-10 11:24  vivid_stareium  阅读(9)  评论(0)    收藏  举报