一些基础的有关图的姿势

版权声明:蒟蒻写的文章,能看就行了,同时欢迎大佬们指点错误 https://blog.csdn.net/Algor_pro_king_John/article/details/80597789

1. Tarjan算法 & 无向图连通性

1.1 无向图的割点和割边
  • 给定无向图 G = { V , E } G=\{V,E\}

    • 定义割点:若对于 x V x\in V ,从图中删去节点 x x ,以及所有与 x x 关联的边之后, G G 分裂成两个或两个以上的不相连的子图,则称 x x G G 的割点

    • 定义割边:若对于 e E e\in E ,从图中删去边 e e 之后, G G 分裂成两个不相连的子图,则称 e e G G 的桥或割边.

1.2 搜索树
  • 在无向连通图中任选一个节点进行 d f s dfs ,每个点只被访问一次,之后所有被访问到的边构成的一棵树称为**“无向连通图的搜索树”**(有向图是类似的)

  • 而对于一般无向图,各个联通块搜索树构成**“搜索森林”**.

  • 对于搜索树上以 x x 为根的子树,我们记为 s u b t r e e ( x ) subtree(x)

1.3 时间戳 以及 追溯值
  • 在遍历一棵搜索树时,每个节点第一次被访问的时间称为这个节点的时间戳.

  • 在Tarjan算法中,最特殊也是最重要的一点就是追溯值,即 l o w [ x ] low[x] ,定义为如下节点的时间戳的最小值:

    • s u b t r e e ( x ) subtree(x) 中的节点.
    • 通过一条不在搜索树上的边能够到达 s u b t r e e ( x ) subtree(x) 中某一点的节点
  • 根据定义我们可以写出如下关系,对于任意从 x x 出发的边 ( x , y ) (x,y) ,有:

    • 若搜索树上 x x y y 的父亲,则 l o w [ x ] = min ( l o w [ x ] , l o w [ y ] ) low[x]=\min(low[x],low[y]) .
    • 反之,这不是一条搜索树上的边,则 l o w [ x ] = min ( l o w [ x ] , d f n [ y ] ) low[x]=\min(low[x],dfn[y]) .
1.4 割边的判定
  • 无向边 ( x , y ) (x,y) 是桥,当且仅当搜索树上存在 x x 的一个子节点 y y ,满足 d f n [ x ] < l o w [ y ] dfn[x]<low[y]
  • 根据定义,我们发现当满足 d f n [ x ] < l o w [ y ] dfn[x]\lt low[y] 时,代表从 s u b t r e e ( y ) subtree(y) 出发,不经过 ( x , y ) (x,y) 的前提下,不管怎么走,都无法到达 x x 或比 x x 时间戳更早的节点.
  • 换言之,若把 ( x , y ) (x,y) 删除,则从根节点 r o o t root x x 这一条路径上的任意一个点都不可能到达 s u b t r e e ( y ) subtree(y) ,则 s u b t r e e ( y ) subtree(y) 形成了一个封闭区域,图也就被分割成了至少两部分,因此 ( x , y ) (x,y) 是桥.
1.5 割点的判定
  • 与割边的判定是类似的,我们可以得到,若点 x x 是割点,当且仅当搜索树上存在 x x 的一个子节点 y y ,满足 d f n [ x ] l o w [ y ] dfn[x]\le low[y]
  • 特殊的,根节点要求至少存在两个子节点 y 1 , y 2 y_1,y_2 ,满足如上条件.
1.6 处理割边 、割点的实现细节 及 代码
  • 在具体实现中,我们应当注意原图是一个无向图.

  • 所以如果直接遍历,每个点都会访问到它的父亲节点,根据 l o w low 的计算, ( x , f a ) (x,fa) 属于搜索树上的边,故不能用 f a fa 的时间戳来更新 l o w [ x ] low[x] .

  • 这时,我们可能会想到记录父亲节点,但这样却处理不了重边(虽然在一般的题目中会保证两点之间至多有一条边)

  • 在重复的边中,只有一条算是“搜索树上的边”,故其他边是可以用来更新 l o w [ x ] low[x] 的.

  • 一个很巧的解决方法与网络流中的“反向边”类似,具体过程不再赘述.

//#define I register int
//#define mn(a, b) ((a) = (a) < (b) ? (a) : (b))

void Tarjan(I k, I edge) {
	dfn[k] = low[k] = ++ cnt; I flag = 0;
	for (I x = las[k]; x; x = nex[x])
		if (!dfn[tov[x]]) {
			Tarjan(tov[x], x), mn(low[k], low[tov[x]]);
			if (dfn[k] < low[tov[x]]) bridge[len[x]] = 1;
			if (dfn[k] <= low[tov[x]]) {
				flag ++, Good[len[x]] = 1;
				if (x ^ root || flag > 1) cut[k] = 1;
			}
		}
		else if (x ^ (edge ^ 1)) mn(low[k], dfn[tov[x]]);
}
  • 那么求出桥和割点后怎么求边双和点双呢?

  • 边双其实很好做:

void Dg(I x) {
	Bm[x] = cnt;
	for (I k = las[x]; k; k = nex[k])
		if (!B[k] && !Bm[tov[k]]) Dg(tov[k]);
}

F(i, 1, n) if (!Bm[i]) ++ cnt, Dg(i);

// Bm相同的点处于同一个点双里.
  • 而点双则比较复杂,如果有 n n 个割点和 m m v D c c v-Dcc ,则我们需要构一个 n + m n+m 个节点的图,每一个割点连向一个点双,注意,一个点可能同时处于多个点双里.
1.7 无向图的双联通分量
  • 若一张无向连通图中不存在割点,则称它为**“点双联通图”.**
  • 若一张无向连通图中不存在,则称它为**“边双联通图”**
    $ $
  • 无向图的极大点双联通子图被称为“点双联通分量”,简称 v D C C v-DCC
  • 无向图的极大变双联通子图被称为“边双联通分量”,简称为 e D C C e-DCC
    $ $
  • 我们称一个双联通子图 G = { V , E } G&#x27;=\{V&#x27;,E&#x27;\} 极大,当且仅当不存在包含 G G&#x27; 的更大子图 G = { V , E } G&#x27;&#x27;=\{V&#x27;&#x27;,E&#x27;&#x27;\} ,满足 V V , E E V&#x27;\in V&#x27;&#x27;,E&#x27;\in E&#x27;&#x27; ,并且 G G&#x27;&#x27; 也是双联通子图.
1.8 例题讲解

1.8.1 例1 【COCI2007】追捕盗贼

  • 题目大意

    • 给一张 n n 个点, m m 条边的无向图 G = { V , E } G=\{V,E\} Q Q 个询问:
      • 删掉边 ( G 1 , G 2 ) (G_1,G_2) 之后,询问 A , B A,B 是否联通.
      • 删掉点 C C 之后,询问 A , B A,B 是否联通.
  • 数据范围

    • N 1 0 5 , M 5 1 0 5 , Q 3 1 0 5 N\le 10^5, M\le 5 * 10^5, Q\le 3 * 10^5
  • 解题思路

    • 这是一道很好的模板题,首先看第一个操作。
    • 题目询问删边,然后是否联通,所以一个套路就是用:边双处理删边连通性问题
    • 把每个边双缩成一个点后,那么删掉一条边 ( G 1 , G 2 ) (G_1,G_2) 影响 A , B A,B 的必要条件是 ( G 1 , G 2 ) (G_1,G_2) 是桥.
    • 随后,我们把每一个边双缩点,然后构出一个新图,容易发现,这个新图上的每一条边其实都是一个桥,且这个图实际上是一棵树,没有返祖边,所以问题转化询问桥 ( G 1 , G 2 ) (G_1,G_2) 是否在 A , B A,B 两点所代表的边双的路径上.
    • 如何判断在路径上呢?假设 d f n [ G 1 ] &lt; d f n [ G 2 ] dfn[G_1]\lt dfn[G_2] ,即 G 2 G_2 G 1 G_1 的父亲,那么我只需要判断一个点在 G 1 G_1 内,一个点不在 G 1 G_1 内,那么 A , B A,B 就一定是会被影响的.
    • 对于第二问,我们可以运用类似的思想.
    • 但这里,注意,我们并不需要刚刚构的那棵新树了,因为这里删的是一个点,而我们求的是边双,删掉一个点,并不代表删掉了新树中的一个点(那是一个边双啊!),所以我们不能用上面的方法.
    • 而处理的方法其实也很简单,我们先把原图对应的搜索树给构出来,那么现在一组询问 ( A , B , C ) (A,B,C) C C 会影响 A B A\rightarrow B 路径的必要条件是 C C 在这条路径上,而我们知道这并不是充要条件,因为搜索树上实际是有返祖边的!
    • 怎么办?我们可以想到割点的判定方法, l o w [ y ] d f n [ x ] low[y]\ge dfn[x] ,则 x x 是一个割点,慢点…,注意到没有,只要满足条件 l o w [ y ] d f n [ x ] low[y]\ge dfn[x] ,则 s u b t r e e ( y ) subtree(y) 中所有节点都会新成一个新的联通块,所以我们 T a r j a n Tarjan 的时候可以处理出一个点 x x 所能影响到的一些联通块,然后遍历搜索树时,优先遍历这些联通块,那么对于每一个节点,我们可以把它能影响到的节点看成搜索树上的一段 d f n dfn 序,然后就可以直接判断了.
    • 另一种处理方法其实也很简单,注意到上面的过程是把影响到的联通块变成一段连续的 d f n dfn ,所以,我们可以很容易的想到,直接从 ( A , B ) (A,B) 两个节点往上跳,假设 A , B A,B 都是 C C 的子树,那么只需要看其中最接近 C C 的两个节点中是否有 l o w low 值大于等于 d f n [ C ] dfn[C] 的,有的话则易知不存在返祖边,即删掉 C C 会有影响,而如果是只有一个节点是 C C 的子树,那也是类似的,只需要判断一个节点的 l o w low 值与 d f n dfn 关系.
    • 整个过程其实十分简单.

1.8.2 例2 Network

  • 题目大意

    • 给定 n n 个节点, m m 条边的无向图 G = { V , E } G=\{V,E\} Q Q 次操作,每次添加一条边,并询问图中桥的数量.
  • 数据范围

    • n 1 0 5 , m 2 1 0 5 , Q 1 0 3 n\le 10^5,m\le 2*10^5,Q\le 10^3
  • 解题思路:

    • 不难想到用边双,然后缩点构成一棵树,每次加一条边 ( x , y ) (x,y) ,当 x , y x,y 不在一个边双里时,实际上就是把 x , y x,y 所代表的边双在树上对应的两个节点的一条路径全部并为一个新的边双.
    • 并查集维护,做到 O ( M + Q l o g N ) O(M+QlogN) .
    • 核心代码:
void dfs(I k) {
	for (I x = LAS[k]; x; x = NEX[x])
		if (!fa[TOV[x]]) dep[TOV[x]] = dep[k] + 1, fa[TOV[x]] = k, dfs(TOV[x]);
}

int get(I x) { return fat[x] == 0 ? x : fat[x] = get(fat[x]); }

void Go(I x, I y) {
	for (; get(x) ^ get(y); ) {
		if (dep[get(x)] < dep[get(y)]) swap(x, y);
		x = fat[x] = get(fa[x]), answer --;
	}
}

1.8.3 例3 Knights of the Round Table

  • 题目大意

    • 给定 n n 个节点,要求构造一个图,使得 m m 对互斥节点 ( x i , y i ) (x_i,y_i) 没有边相连,询问有多少个节点不能出现在图中.
  • 数据范围

    • n 1 0 3 , m 1 0 6 n\le 10^3,m\le 10^6
  • 解题思路:

    • 构出原图的补图,即任意没有互斥关系的节点连边,然后问题就等价于询问其中有多少个点不在任意奇环里.

    • 这个问题有一个经典的方法,就是构造出原图的点双.

    • 考虑每一个点双,其拥有一个性质:

      • 任意不在同一点双两个节点 ( x , y ) (x,y) ,一定不能同时出现在构造图中.
    • 我们尝试着证明一下:

      • 还是用反证法.
      • 假设这两个点 ( x , y ) (x,y) 分别处于点双 ( c u t [ x ] , c u t [ y ] ) (cut[x], cut[y]) 中,并且它们同时出现在一个奇环里.
      • 既然 x , y x,y 都处于点双,那么割掉点双中任意一个点原图依然联通,且因为 x , y x,y 同处于一个环里,那么环上任意一点割掉后 x , y x,y 依然联通,故可以形成一个更大的点双,与点双的极大性矛盾.
      • 原命题成立.
    • 那么现在,我们把点双独立,即我们只需分别判断一个个点双是否能构到新图中.

    • 点双能构到新图中的必要条件是这个点双里含至少一个奇环.

    • 但是不是充要条件呢?

    • 我们可以发现,假定其中存在一个奇环,用 ( x 1 , x 2 , &ThinSpace; , x t ) (x_1,x_2,\cdots,x_t) 表示,那么对于点双中除奇环外的任意一个 y y ,就必定存在奇环中的两个点 x i , x j x_i,x_j ,使得 x i , x j , y x_i,x_j,y 同时处于同一个简单环中,否则一定可以割掉环上的某个点使得环与 y y 不联通,这与点双没有割点矛盾.

    • 同时,经过上面分析我们可以发现,既然存在 x i , x j , y x_i,x_j,y 处于一个简单环,且奇环中 x i x j x_i\rightarrow x_j 有两条长度分别为奇数和偶数的路径,所以不管 x i l e n y   +   x j l e n y   +   x i l e n x j x_i \rightarrow_{len} y\ + \ x_j\rightarrow_{len} y\ + \ x_i\rightarrow_{len} x_j 长度是奇还是偶,都一定组合成一个奇环.

    • 故我们发现只要一个点双里含有一个奇环,则这个点双上所有的点都一定可以出现在图中.

    • 反之,如果没有奇环,则一定都不能出现在图中.

    • 判断奇环我们可以直接用二分图匹配,暴力的在每一个点双中找.


2. Tarjan算法 & 有向图连通性

2.1 流图 和 源点
  • 给定有向图 G = { V , E } G=\{V,E\} ,若存在 r V r\in V ,满足从 r r 出发能够到达 V V 中的所有点,则称 G G 是一个流图,记为 ( G , r ) (G,r) ,其中 r r 称为流图的源点.
2.2 四种边
  • 流图中每条边 ( x , y ) (x,y) 必定是以下四种之一:

    • 树枝边,指搜索树中的边,即 x x y y 的父亲.
    • 前向边,指搜索树中 x x y y 的祖先节点.
    • 后向边,指搜索树中 y y x x 的祖先节点.
    • 横叉边,除以上三种情况的边,即 d f n [ y ] &lt; d f n [ x ] dfn[y]&lt;dfn[x] .
2.3 有向图的强联通分量
  • 与求割点的思想类似,只不过这里,我们要求当一个点满足 d f n [ x ] = l o w [ x ] dfn[x]=low[x] 时,则在搜索树上以 x x 为根的子树是一个强联通分量.

  • 具体实现我们可以用一个栈.

2.4 例题讲解

2.4.1 例1 Network of Schools

  • 题目大意

    • 给定一张有向图
      • 求至少需要染色多少个点,使得这些染色点能把整个图给染完色,染色时只能沿有向边传递.
      • 求至少需要添加多少条有向边,使得这张有向图变为强联通图.
  • 数据范围

    • n 100 n\le 100
  • 解题思路

    • 对于第一问,我们可以想到有向图的强联通分量,然后缩点,构成一张新图,那么答案很显然就是新图中“零入度点”的个数.

    • 对于第二问,是一个经典的问题,我们也可以在第一问的基础上去更进一步.

    • 缩完点之后的图实际上是一个森林,把问题简单化,先考虑对于一棵树,

3. 欧拉回路 及 欧拉路径

3.1 基本概念
  • 欧拉回路
    • 图中经过每条边一次并且仅一次的回路称作欧拉回路。
  • 欧拉路径
    • 图中经过每条边一次并且仅一次的路径称作欧拉路径。
  • 欧拉图
    • 存在欧拉回路的图称为欧拉图。
  • 半欧拉图
    • 存在欧拉路径但不存在欧拉回路的图称为半欧拉图。
3.2 无向图中 欧拉图及半欧拉图的判定
3.2.1 定理①
  • 无向图G为欧拉图,当且仅当G连通,且所有顶点的度为偶数
3.2.2 定理②
  • 无向图G为半欧拉图,当且仅当G连通,且除了两个顶点的度为奇数之外,其它所有顶点的度为偶数
3.3 有向图中 欧拉图及半欧拉图的判定
3.3.1 定理①
  • 有向图G为欧拉图,当且仅当G的基图$连通,且所有顶点的入度等于出度
3.3.2 定理②
  • 有向图G为半欧拉图,当且仅当G的基图连通,且存在顶点 u u 的入度比出度大 1 1 、顶点 v v 的入度比出度小 1 1 ,且其它所有顶点的入度等于出度
3.4 求解欧拉回路及欧拉路径的算法——Hierholzer算法
  • 算法思路:

    • 从起点依次递归.
    • 把已经没有出边的点依次加入到队列里,然后回溯到第一个有出边的点,继续递归.
    • 最后倒序输出.
  • 算法流程(无向图):

    • 判断奇点数。奇点数若为 0 0 则任意指定起点,奇点数若为 2 2 则指定起点为奇点。
    • 开始递归函数Hierholzer(x):
      • 循环寻找与x相连的边(x,u):
        • 删除(x,u)
        • 删除(u,x)
        • Hierholzer(u)
      • 将x插入答案队列之中
    • 倒序输出答案队列
3.5 例题讲解

3.5.1 例一 【NOI2016模拟7.16】人生的经验

  • 题目大意

    • 给定大小为 c c 的字符集,求出一个包含长度为 l l 的所有 c l c^l 个字符串的最短母串 S S .
  • 数据范围

    • S 10000000 |S|\le 10000000
  • 解题思路

    • 首先分析答案长度的上下界,上界 l c l l*c^l 显然可以达到,下界是 c l + l 1 c^l+l-1 ,先来证明这个下界可达.
    • 我们以长度为 l 1 l-1 的前缀作为顶点,那么显然有 c l 1 c^{l-1} 个顶点,每个顶点向外连出 c c 条边,原题转化为在这个图中找一条欧拉回路.
    • 因为每个顶点的入度和出度都是相同的,根据有向图定理①可以得到此图为欧拉图,所以下界可达.
    • 找欧拉回路我们就用Hierholzer算法.
    • 注意此题卡栈卡空.

猜你喜欢

转载自blog.csdn.net/Algor_pro_king_John/article/details/80597789