图论基础

耳分解

对于图 \(G=(V,E)\),还有集合 \(S\),定义耳为一个顶点序列 \(a_1,a_2...a_m\),其中 \(a_1,a_m \in S\)\(a_2..a_{m-1} \in V-S\),且 \(a_i\)\(a_{i+1}\) 之间均有连边。

耳分解就是将图的生成过程写为 \(G_0,G_1...G_t\),其中 \(G_0\) 为一个环,\(G=G_t\)\(G_{i}\) 去掉 \(G_{i-1}\) 就是一个耳。

有向图强联通等价于有向图存在耳分解
无向图双联通等价于无向图存在耳分解。

QOJ3301. Economic One-way Roads

需要找到一个刻画强联通图的方式,这里用耳分解,可以从一个点出发不断通过加一个环的一部分(起点和终点都在已加入的集合里)的方式来得到一个强联通分量。

我们设 \(f_s\) 表示能否通过耳分解得到 \(S\),转移的时候枚举另外一个集合 \(T\) 表示加入集合,这个复杂度太高了。我们改一下,设 \(g_{s,u,v}\) 表示在 \(S\) 集合时,目前的耳构造到了 \(u\),我们的终点是 \(v\) 的最小代价。

枚举集合 \(S\),首先先用 \(g_{s,i,j}+G_{i,j}\) 更新答案 \(f_s\),其中必须满足 \(i,j \in S\) 表示已经构造到最后一步了然后用边 \((i,j)\) 连接。然后需要注意一个点,就是我们求出 \(f_s\) 之后,应该用 \(f_s\) 反向更新 \(g_{s,i,j}\)。然后再用 \(g_{s,i,j}\) 向外扩展,枚举 \(u\)\(g_{s\cup{u},u,j} \gets g_{s,i,j}+G_{i,u}\)。还有一个细节就是如果 \(i=j\),我们要是只往外扩展一个点,可能会出现更新 \(g\) 时用 \(i\to u\),然后更新答案的时候又用 \(u\to i\),这就代表一条边被正反使用了,不符合要求,于是当 \(i=j\) 的时候,我们应该寻找两个不同 \(u\) \(v\) 强制将两个端点转到 \(u\) \(v\) 这样就行了,时间复杂度 \(O(2^nn^3)\)

注意细节由于 dp 的时候是只加入对于强连通分量有贡献的边,但是这并不代表其他边不要定向,所以我们应该提前按照费用小的那边先给所有边定向,然后记录一下费用大的边翻转的费用。

P5776 [SNOI2013] Quare

图上计数

P1989 无向图三元环计数

对于无向图三元环计数,我们可以以度数为第一关键字,以编号为第二关键字对于原图上的无向边进行定向(由小连到大)。这样子我们连出来的就是一个 DAG。然后我们枚举 \(u\),再枚举 \(u\) 的出边到达点 \(v\),接着枚举 \(v\) 的出边到达点,看看是否存在 \(w\),使得图上有 \(v\to w\) 边。
分析一下时间复杂度,我们可以统计每条边的贡献,每条边贡献了 \(out_v\) 。对于原图上度数小于 \(\sqrt m\) 的点,这显然成立。对于度数 \(>\sqrt m\) 的点,我们可以发现它连向点的度数必然是大于等于该点的,这样子的边不超过 \(\sqrt m\) 种。于是时间复杂度是 \(O(m\sqrt m)\) 的。

LOJ191. 无向图四元环计数

还是同样的方法给边定向,但是注意要大连小。然后可以发现一个四元环是由某点出发往外连了两条无向边,然后从无向边的两个终点再往外连两条有向边,两条有向边交于一点,这一点就是环上的第四个点。为了防止重复要求第四点的 \(rk\) 大于第一点。

考虑如何快速实现以上过程,我们只枚举其中一半的边,通过一条无向边加一条有向边找到第四个点,然后给该点的计数器加一,最后该点的贡献就是 \(\dfrac{cnt_i\times(cnt_i-1)}{2}\)

时间复杂度 \(O(m\sqrt m)\)

最短路

最短路图

UVA1416 Warfare And Logistics

先考虑最基本暴力,删除每一条边然后每一个点跑一遍最短路。可以对每个点建出最短路图,如果不在图上的边被删除就不要跑最短路了。

AT_joi2016ho_c 鉄道運賃 (Train Fare)

法一:最短路图

法二:我们可以发现如果增加一条边的边权后,还希望修改后的最短路长度与修改前相同的话,那么必然修改后不会经过这条边。于是等价于将这条边删去。

但是删边操作后不容易修改最短路,可以考虑倒序操作,将删边变成加边,也就是先把所以操作边删去,再倒着扫描加进去,这样就好办多了。我们只需要在加入一条边后更新一下最短路即可。更新如果全局重做的话复杂太高,可以找出哪些点受影响,也就是先找到被操作边两端连接点,用最短路小的去更新大的,如果更新失败直接返回,否则将更新后的点入队。然后 bfs 一遍去继续更新。注意更新条件为如果更新后路径长度为最短路长度,才能更新,否则更新后也不是最短路对答案也就没有影响,不如等到最后可以是最短路了再一并更新。顺便说一句,本题和洛谷上另一道一模一样的题的数据很水,只要满足路径长度缩小而非最短路就更新也能过,但这这种方法可以构造数据 Hack 掉。

Kruskal重构树

不常用,但有奇效,写一篇记录一下性质,防止忘掉。
与 Kruskal 过程相同,只不过是多了一个新建节点,连接一条边的两个点,其点权等于边权。

  1. 注意若原图不连通,构建出来的是森林
  2. 若按边权升序排序则构建出来的是大根堆,反之为小根堆
  3. 若按边权升序排序,则 \(lca(u,v)\) 代表原图中 \((u,v)\) 路径上最大边权的最小值。反之则为最小边权的最大值。
  4. 叶子节点为原图节点,其余节点为原图中的边。由此也可以推断出(不是森林时)会多 \(n-1\) 个的节点,共计 \((2n-1)\) 个节点。
  5. 初始化并查集注意是 \(2n-1\) 个点。

应用 1.\((u,v)\) 路径上最大边权的最小值 P1967 [NOIP2013 提高组] 货车运输

2.从 \(u\) 出发,只经过边权不超过 \(x\) 的边可以到达的点 P7834 Peaks,此时这些点构成重构树中的一个子树。

树的直径

两次 \(bfs\) 可以记录路径,但边权必须非负

\(dp\) 边权任意,但不方便记录路径

性质:树的直径不唯一,所有直径必定相交于中心处(可以为一条边或者为一个点)

P6845 [CEOI2019] Dynamic Diameter

考虑如何刻画直径,如果是两次 dfs 那样显然是不适用于多次查询因为没有一个明确的表达式。
我们可以按照树上 dp 那样子方法来求解,因为那有一个明确的表达式。于是即求 \(\max \{\ dep_u+dep_v-dep_{lca} \}\)。待修改的多次查询启发我们用数据结构维护,于是将树转化为欧拉序,于是 \(dep_{lca}\) 就转化为 \([\min(l_u,l_v),\max(r_u,r_v)]\) 中的最小值。考虑到减去的最小值,于是我们只需要求 \(\max\limits_{i\le j\le k}\{a_i+a_k-a_j\}\)
这可以用线段树维护,我们可以拆开维护再合并,维护最大的 \(a_i-a_j\)\(-a_j+a_k\) 还有 \(a_i~a_k\) 维护的时候搭配一下即可。

CF842E Nikita and game

支持动态加叶子节点的树的直径

二分图

二分图最大匹配

0要素 每个集合内部有0条边

1要素 每个节点只能和一条匹配边相连

二分图最小点覆盖

2要素 每条边两个端点,二者至少选一个

常见应用:完成一个任务有两种方式,两者选其一即可。

树的重心

性质:

  1. 某个点是树的重心等价于它最大子树大小不大于整棵树大小的一半。

  2. 树至多有两个重心。如果树有两个重心,那么它们相邻。此时树一定有偶数个节点,且可以被划分为两个大小相等的分支,每个分支各自包含一个重心。

  3. 树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。反过来,距离和最小的点一定是重心。

  4. 往树上增加或减少一个叶子,如果原节点数是奇数,那么重心可能增加一个,原重心仍是重心;如果原节点数是偶数,重心可能减少一个,另一个重心仍是重心。

  5. 把两棵树通过一条边相连得到一棵新的树,则新的重心在较大的一棵树一侧的连接点与原重心之间的简单路径上。如果两棵树大小一样,则重心就是两个连接点

  6. 一棵树的重心一定在根节点所在的重链上

LCA

trick 树上三点到一点距离和最小,该点即为求出两两 \(lca\),不重合的那个。
判断树上路径 \((s,t)\)\((a,b)\) 是否相交,就是看是否一条路径的顶点(lca)在另一条路径上。

dfs序求lca

\(u\) 不是 \(v\) 祖先,查询二者之间 dfn 序最小的节点,它的父亲就是 lca。
否则查询 \([dfn_u+1,dfn_v]\) 之间的。
同样的情况一也可以推广到第二种情况的那种查询。

最小生成树

P3623 [APIO2008] 免费道路

考虑最直接的做法,一直加 \(k\) 条鹅卵石边,然后再加水泥边,这显然可能导致最后连不出来一棵树。问题在于我们如果在多条鹅卵石边中选出正确的 \(k\) 条边,使得尽可能让水泥边可以连成一棵树。

考虑进行水泥边优先的 Kruskal,那么在这颗生成树中的加入的鹅卵石边就是一些必选边。其他鹅卵石边显然是效果和必选鹅卵石边重复或者可以被水泥边替换的。于是我们再来一次变式的 Kruskal,先加入必选鹅卵石边然后再加入鹅卵石边直到数量达到 \(k\),最后再加入水泥边。

【UER #1】DZY Loves Graph

动态维护带撤销的最小生成树。
如果只是加边的话,因为加入的边单增,所以我们当它可以连成树的时候记录答案,此时答案就不变了。

带上删除操作,我们可以可持久化并查集,每次删除直接回退就行了,删除的次数最多为加边次数 \(O(m)\)。同时记录每个状态的是否连成树了,回退到某个状态如果已经连成树了就直接输出那个状态的答案,理由和上面那个一样。

如果还有回退操作,可能会让我们上述的均摊变得不正确,我们可以离线下来,如果删除后一个操作不是撤回就删除,如果是撤回这次就不删了,直接统计答案。反正下次就撤回来了。

Tarjan

强连通分量

找到有向图强连通分量,也就是对于每个点找到和它构成环的所有点。

我们只要在 dfs 的过程中维护一个栈 \(s\),保存的是 \(x\) 的祖先节点 \(anc(x)\) 和那些已经访问过的存在一条路径到 \(anc(x)\) 的节点。这个第二类的点的由来就是假设目前在 \(x\) 点进行统计,那么栈里面除了它的祖先还有它子树内一些连上来的边。这些点都是在一个强连通分量中,所以我们不能在每个儿子回溯之后立即统计,而是应该等到所有儿子都 dfs 完之后再统计。

对于 \(v \in s\),如果存在一条边 \(u\to v\),那么就代表 \(u\)\(v\) 在同一个环中。

定义 \(low_x\) 表示 \(x\) 的追溯值,\(low_x=\min\limits_y\{dfn_y\}\),其中 \(y\) 满足在 \(x\) 子树中或者存在一条从 \(x\) 子树内出发的边可以抵达 \(y\)

我们执行以下流程:注意以下 \(\gets\) 符号基本为取 \(\min\)

  • \(x\) 被第一次访问的时候,将 \(x\) 入栈,并 \(low_x \gets dfn_x\)
  • 扫描 \(x\) 所有出边 \((x,y)\),如果 \(y\) 没有被访问过,那么继续搜索 \(y\),并且 \(low_x\gets low_y\);如果 \(y\) 被访问过且 \(y\) 在栈中那么 \(low_x\gets dfn_y\)
  • \(x\) 回溯之前,判断是否有 \(low_x=dfn_x\) 成立,若成立,就不断从栈中弹出节点,直到 \(x\) 出栈。

在以上过程中,若从 \(x\) 回溯前有 \(low_x=dfn_x\) 成立,那么栈中从 \(x\) 到栈顶的所有节点构成一个强联通分量。

缩点之后,我们得到的是有向无环图

P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G

我们对于建立反边,题目就转化为有多少点可以到达全局所有点。
可以先缩点一下,然后变成了一个 DAG,我们直接 bfs 即可。

P3387 【模板】缩点

缩点之后 topo 排序过程中 dp 即可。

在 DAG 上有,\(dp_v\gets dp_u+val_v\)

点双

条件是 \(low_v\ge dfn_u\)
几个小细节,特判独立点。
特判根节点度数为 \(1\) 的时候不能成为割点。

\(x,y\) 的必经点是 \(z\),那么 \(z\) 是割点。

\(x\)\(y,z\) 分别双联通,且 \(y,z\) 不双联通的时候,\(x\)\(y-z\) 的必经点。

边双

条件是 \(low_v>dfn_u\)
注意一下记录一下入边,不能从入边转移。

圆方树

就是对于原图进行点双计算,每当找到一个点双,我们就建立一个方点,然后将点双中的点都当做圆点,和这个方点连边,有点菊花图的感觉。

有个问题就是如果对于点双内所有点都这么连边,按理来说这些点都是对称的,那么如何突出割点的地位?其实关键在于一个点可以作为很多个点双的割点,或者被包含于不同点双,所以我们在弹出当前点双内的点的时候,其实还保留了割点在栈中,这样子割点就会再被包含于其他点双,从而与其他方点连边,这也从另一个方面证明了圆方树中圆方交替出现的事实。同时也证明了圆点 \(x\) 的度数等于包含他的点双的数量。也证明了原图中的某点是割点当且仅当它在圆方树中是叶子节点。

圆方树的作用就就是很好地刻画了两点之间的联通性,对于圆方树上两点他们路径上的点就是原图上的必经点。圆方树上删除某点后的连通性和原图上删除该点的联通相同。

模板题。P5058 [ZJOI2004] 嗅探器

差分约束

差分约束系统字典序,可通过对 \(0\) 节点赋值实现。

约束类

P3243 [HNOI2015] 菜肴制作 正着考虑字典序最小是错解,因为无法保证 \(1\) 更早到达,正确的应该是走到 \(1\) 的最短路径中最优的一条。会发现要求非常 "紧迫",因为要求以最快速度填入 \(1\),然后是 \(2\) 以此类推。同时满足先到 \(1\) 和路径更优这两个条件,这样操作空间不大。但是如果我们建反图考虑的话,发现反正是尽可能后的放 \(1\) 所以并不着急放 \(1\), 可以随时放。这就从容许多。于是因为没有“先到 \(1\) ” 这个任务,那就可以直接字典序了。
类似 图/树上约束到达顺序题目:
P9755 [CSP-S 2023] 种树
P1954 [NOI2010] 航空管制
CF1765H Hospital Queue
[ABC304Ex] Constrained Topological Sort

欧拉回路

经过联通图每条边恰好一次的路径被称为欧拉回路
如果这条路径为回路,那么就是欧拉回路。
存在欧拉回路的图称为欧拉图,不存在欧拉回路但存在欧拉路径的称为半欧拉图。

有向图

有向图 \(G\) 为欧拉图当且仅当 \(G\) 弱联通且每个点的出度等于入度。
有向图 \(G\) 为半欧拉图当且仅当 \(G\) 弱联通且存在两个点入度分别等于出度减 \(1\) 或者出度加 \(1\),其余点入度等于出度。

无向图

无向图 \(G\) 为欧拉图当且仅当 \(G\) 弱联通且每个点度数为偶数。
无向图 \(G\) 为半欧拉图当且仅当 \(G\) 弱联通且存在两个点度数为奇数,其余点度数为偶数。

混合图

对于混合图有一部分边为有向边,一部分为无向边。首先,将有向边改为无向边后图必须联通。满足上述条件后,我们只需要对于无向边定向,使得对于每个节点出度等于入度即可。

基环树

对于环的两种基本处理方式:直接断环,两次 dp。或者断环为链,复制一遍,转化为序列问题。

P10933 创世纪

这一类约束型就肯定选择两次 dp。

对于两次 dp 类的基环树,我们只需要找到环上的相邻两个点即可,不需要找出整个环,可以用并查集寻找基环树森林的环上相邻点。假设某颗树是 \(s\to t\),那么就先断这条边,正常 dp 一次,然后 \(ans\gets\max(dp_{t,0},dp_{t,1})\),然后强制不选 \(t\),那么同时记录下 \(s\) 代表可以随意选。最后 \(ans\gets dp_{t,0}\)

P4381 [IOI2008] Island

最优选择类就是断环为链了。

这种需要找到所有环上点,可以采用拓扑排序,由于环上的度数最小也只能缩小到 \(2\),所以不可能被标记。最后所有 \(deg_u=2\) 的点就是环上点了。

最后在链上就是一个简单单调队列优化 dp 就行了。

posted @ 2024-01-06 23:56  Mirasycle  阅读(3)  评论(0编辑  收藏  举报