P2860 [USACO06JAN] Redundant Paths G
就是说通过加边,使得图中的每一点都不是割点,就是让连通图中没有桥,没有割点
并非是桥的数量-1
边双连通分量即一个无向图中,去掉一条边后仍互相连通的极大子图。(单独的一个点也可能是一个边双连通分量)
换言之,一个边双连通分量中不包含桥。
思路是
通过tarjan算法,这样所有弹出的点都归属于一个边-双联通分量。
将每个节点所归属的边-双连通分量后,我们可以将归属于一个边-双连通分量缩成新图上的一个节点,这样所形成的每个连通分量就是一棵树。然后我们需要证明一个定理:这个连通分量上的广义叶子节点(度数为1)除以2向上取整即为所需要加的边数。
题目要求的是所有点至少度数为2,度数为1的点应该至少连一条边,最好的方法当然是一次性连两个度数为1的点,如果最后没有匹配(个数为奇数),仍然要连边,所以得出结论。
就是说,要求树里的点度数都至少为2,然后先是连边,连度数都为1的点,这样的话每条边都能消掉2个度为1的,这样的边共可以消掉x*2个度数为1的点,如果点的个数为偶数,那么恰好可以消完,但是如果是奇数,就会剩一个,此时就需要连边到度数不为1的上,此时这个边就只能消掉一个,而且仅存在这一条边,为奇数时;
不为奇数时,由于除法向下取整,所以也不会影响,所以就是Leaf+1/2
关键在于第二部缩点
首先我们要明白: 进行 e-DCC 缩点之后,最终形成的图是一棵树。
因为: 假设缩点之后的图不是树,那么一定存在一个环,因此这个环还可以缩成一个点,这产生了矛盾。
实际上等到我们把原图中所有的桥找出来以后,对不含桥的连通块缩点,会发现最后剩下的图是一个树。添加最少的边使这棵树上所有的链都变成环,则是本题的答案。这个答案可以由这棵树叶子节点数/2向上取整得出。(贪心,比较显然)
要通过Tarjan算法完成无向图的缩点,可以进行以下步骤:
1. 定义一些全局变量和数据结构。包括一个数组`dfn`和一个数组`low`,分别用于记录每个顶点的发现时间和低链接值;一个堆栈`sta`用于存储遍历过程中的顶点;一个整数`num`用于记录当前时间;一个整数`col`用于记录缩点后的新顶点的编号;一个数组`color`用于记录每个顶点所属的缩点后的新顶点。
2. 定义一个函数`tarjan`,该函数接受一个顶点参数`u`。在函数内部,首先将当前顶点的发现时间和低链接值都初始化为当前时间`num`,然后将当前顶点入栈。
3. 遍历当前顶点的邻接边,对于每条边 `(u, v)`,进行以下操作:
- 如果顶点`v`未被访问过(即`dfn[v]`等于0),则递归调用`tarjan`函数,并更新当前顶点的低链接值为当前顶点的低链接值和顶点`v`的低链接值中的较小值。
- 如果顶点`v`已被访问过,并且它不是当前顶点的父节点,则更新当前顶点的低链接值为当前顶点的低链接值和顶点`v`的发现时间中的较小值。
4. 在当前顶点的低链接值等于发现时间的情况下,说明找到了一个连通分量。为了区分不同的连通分量,使用一个计数器`col`,将当前顶点和栈中的顶点都标记为同一个新顶点,即将`color`数组中对应的位置设置为`col`。
相等是找到一个强联通分量,如果dfn小于儿子,说明儿子有别的路回到更远;如果大于,说明儿子到不了父亲;就是说,每个连通分量里,有且仅有一个点满足相等的条件
在每个联通分量中,有且仅有一个点的低链接值等于发现时间。这个点通常称为“根”或“割点”。
这是因为在Tarjan算法中,对于一个联通分量而言,从任意一个顶点开始进行DFS遍历时,首个被访问到的顶点一定是“根”,也即发现时间最早的顶点。在这个根顶点的DFS子树中,通过向下逐层遍历,我们会发现在这个子树上其他的顶点的发现时间都不小于根顶点的发现时间。
由于Tarjan算法是通过DFS遍历来计算低链接值的,对于每个顶点,低链接值是根据它所能达到的最早发现时间的顶点进行更新的。因此,只有根顶点的低链接值等于发现时间,其他顶点的低链接值都大于根顶点的发现时间。
所以,在每个联通分量中,只有一个顶点的低链接值等于发现时间,其他顶点的低链接值都大于发现时间。这个顶点被称为割点,它是决定联通分量的关键点。
5. 在主函数中进行图的遍历。对于每个未被访问的顶点,调用`tarjan`函数来计算连通分量。
6. 最后,得到缩点后的图。新图的顶点数量为`col`,可以将原图中的每个顶点映射到新图中的对应顶点。根据缩点后的信息,可以进行后续的缩点操作,例如计算新图的入度、出度等。
通过以上步骤,就可以使用Tarjan算法完成无向图的缩点操作。这样可以将原始的图转换为一个更简化的图结构,方便进行后续的图算法处理。
这段代码是使用递归实现的深度优先搜索(DFS)算法,用于在有向图中找到强连通分量(SCC)。下面是代码的解释:
- 函数`dfs`以一个整数`cur`作为输入,表示当前正在访问DFS遍历中的节点。
- 变量`dfn`和`low`是数组,用于存储每个节点的DFS编号和低链接值。
- 变量`index_`用于跟踪每个节点的DFS编号。
- for循环遍历从当前节点`cur`出发的所有边。
- if条件检查边是否有效(即`tf[j]`为真)。如果是,则继续执行代码。
- 在if条件内部,代码检查目标节点`i`是否已被访问过(即`dfn[i]`为非零)。如果是,则使用`i`的DFS编号更新`cur`的低链接值。
- 如果目标节点`i`之前没有被访问过,代码将`i`推入一个栈`st`,标记当前边及其反向边为已访问(即将`tf[j]`和`tf[(j&1)?j+1:j-1]`设置为假),并递归调用`dfs`函数来访问`i`。
- 递归调用结束后,代码通过将当前低链接值与`i`的低链接值的最小值来更新`cur`的低链接值。
- 如果`cur`的DFS编号和低链接值相等,则说明`cur`是一个新的SCC的根节点。
- 代码将`ans`变量加1,以计算SCC的数量。
- 然后,代码从栈`st`中弹出节点,并将它们分配给当前SCC,直到遇到`cur`节点。
- 最后,代码将SCC编号(`ans`)存储在弹出的节点中,即将其存储在`bcc`数组中。
注意:代码假设图的邻接表表示存储在数组`h`,`nxt`和`p`中。栈`st`用于存储当前SCC中的节点。`tf`数组用于跟踪哪些边已经被访问过。
这行代码是在标记强连通分量时的一部分操作。
代码的意思是,当栈顶的节点不等于当前节点`u`时,将栈顶的节点(`sta[top]`)标记为属于当前的强连通分量(使用col来表示当前的强连通分量编号),然后将栈顶的指针`top`减1,即将栈顶节点出栈。
这行代码的作用是将栈中保存的一系列节点都标记为属于当前的强连通分量。由于Tarjan算法中,栈中的节点是按照DFS的顺序依次入栈的,而在找到一个强连通分量时,栈中的节点都是属于该强连通分量的。因此,这行代码将栈中的节点依次出栈,并将它们标记为当前的强连通分量。
请注意,这行代码前面的`top--`表示减小栈的指针,将栈顶指针移动到下一个位置,即将栈顶元素出栈,而在对栈顶元素进行标记后,`top--`是在执行标记后进行的。
就是说由于是递归dfs,所以先访问的最后才会出来,但是对栈的修改是在每层递归里都进行的,在栈中时,每个联通分量只会有一个是相等的关系,是这一组结点的根,在这个栈中结点之上的其他结点,它门的low都是比自己的dfn小的,而且等于它们的根结点
这段代码是一个寻找图中割点(Cut Point)数量的算法,并输出结果的实现代码。下面是代码的解释:
- `N`和`M`分别表示数组大小的常量,其中`N`为图中节点的最大数量,`M`为图中边的最大数量。
- `n`和`m`分别表示图中节点的数量和边的数量。
- `vis`是一个大小为`2*M`的数组,用于标记边是否已经被访问过。
- `du`是一个大小为`N`的数组,用于记录每个节点的度数。
- `ans`表示割点的数量。
- `cnt`表示边的计数器。
- `head`是一个大小为`N`的数组,用于存储每个节点的边链表的头节点。
- `u`和`v`是大小为`M`的数组,用于存储边的起点和终点。
函数解释:
- `add`函数用于向图中添加一条边。它接受两个参数`u`和`v`,表示边的起点和终点。在添加边的过程中,会更新边链表和度数数组。
- `tarjan`函数是Tarjan算法的实现,用于寻找强连通分量并标记割点。它接受一个参数`u`,表示当前正在访问的节点。在函数中,通过DFS遍历将图中的节点分为强连通分量,并标记割点。
- `main`函数是程序的主函数。在函数中,首先对数组进行初始化。
- 然后,通过输入获取图中的节点数量和边的数量,并利用`add`函数添加边。
- 接下来,使用`tarjan`函数找到强连通分量和割点,并标记割点的数量。
- 最后,输出割点的数量的一半。
`u`和`v`数组分别用于存储输入的每条边的起点和终点。在`main`函数中,通过`scanf`函数输入这些数据,然后通过`add`函数将这些边添加到图中。这样做是为了方便进行Tarjan算法的实现和处理割点的标记。
需要注意,这里缩点,vis数组记录的是对边的访问情况,