图论——连通性

欧拉回路

定义

欧拉回路:图 G G G中经过每条边一次的回路。
欧拉路径:图 G G G中经过每条边一次的路径。
欧拉图:存在欧拉回路的图。
半欧拉图:存在欧拉路径且不存在欧拉回路的图。

判定

定理1:无向图 G G G为欧拉图,当且仅当 G G G连通且每个点的度数均为偶数。
推论1:无向图 G G G为半欧拉图,当且仅当 G G G连通且除了两个点度数为奇数外,其余个点度数均为偶数。
定理2:有向图 G G G为欧拉图,当且仅当 G G G的基图连通且每个点入度等于出度。
推论2:有向图 G G G为半欧拉图,当且仅当 G G G连通且除了 u u u入度比出度大 1 1 1 v v v出度比入度大一外,其余个点入度均等于出度。

性质

性质1:设 C C C为欧拉图 G G G中一条简单回路,将 C C C中的边从图 G G G中删去后得到 G ′ G' G G ′ G' G的每个极大连通子图均为欧拉图。
证明

  1. G G G为无向图,则 G ′ G' G满足定理1
  2. G G G为有向图,则 G ′ G' G满足推论1

性质2:设 C 1 、 C 2 C_1、C_2 C1C2为图 G G G中没有公共边,但至少有一个公共点的简单回路,则 C 1 、 C 2 C_1、C_2 C1C2可以合成一个新的回路。
证明:略。。。

求解

bool vis[M << 1]; // 每条边是否被访问

void dfs(int u) {
  for (int i = G[u]; i != 0; i = e[i].nxt) {
    int v = e[i].v;
    if (vis[i]) continue;
    vis[i] = vis[i ^ 1] = true;
    dfs(v);
  }
}

Tarjan 算法

Tarjan算法基于图的DFS遍历。

时间戳

在DFS时,按照每个点第一次被访问的顺序,标记每个结点。该标记称为时间戳,记作 d f n ( u ) dfn(u) dfn(u)

搜索树

在DFS时,每个结点只会被遍历一次。若图连通,则所有连接一个未访问结点的边会构成一棵树,不连通则构成一个森林。

追溯值

u u u出发能访问到的点中, d f n dfn dfn的最小值,称为追溯值,记作 l o w ( u ) low(u) low(u)

强连通分量

定义

有向图的极大强连通子图,称为强连通分量,简称SCC。

算法流程

  1. 枚举每个为访问的结点进行DFS,并将结点压入栈中。
  2. 在DFS时计算 d f n dfn dfn l o w low low
  3. 若当前访问结点 u u u满足 d f n ( u ) = l o w ( u ) dfn(u)=low(u) dfn(u)=low(u),则栈中 u u u及其之后的结点从栈中弹出,这些结点构成一个强连通分量。
  4. 重复以上过程,直至所以结点都被访问。
int scc_num; // 强连通分量个数
int belong[N]; // 每个点所属的强连通分量编号
int dfn[N], low[N]; // 时间戳、追溯值
int S[N], top; // 栈
bool inS[N]; // 是否在栈中

void Tarjan(int u) {
  static int now = 0;
  dfn[u] = low[u] = ++now;
  S[++top] = u, inS[u] = true;
  for (int i = G[u]; i != 0; i = e[i].nxt) {
    int v = e[i].v;
    if (dfn[v] == 0) {
      Tarjan(v);
      low[u] = min(low[u], low[v]);
    } else if (inS[v]) {
      low[u] = min(low[u], dfn[v]);
    }
  }
  if (dfn[u] == low[u]) {
    ++scc_num;
    int v;
    do {
      v = S[top--], inS[v] = false;
      belong[v] = scc_num;
    } while (u != v);
  }
}

割点

定义

在一个无向图中,如果有一个顶点集合,删除这个顶点集合以及这个集合中所有顶点相关联的边以后,图的连通分量增多,就称这个点集为割点集合。
如果某个割点集合只含有一个顶点 v v v(即{ v v v}是一个割点集合),那么 v v v称为一个割点。

判定

  1. u u u为当前搜索树的根结点且 u u u的子树数量大于 1 1 1
  2. u u u不为根且存在树边 ( u , v ) (u,v) (u,v)使 d f n ( u ) < = l o w ( v ) dfn(u)<=low(v) dfn(u)<=low(v)
int root;
bool cut[N]; // 判断是否为割点

void Tarjan(int u) {
  static int now = 0;
  dfn[u] = low[u] = ++now;
  for (int i = G[u], cnt = 0; i != 0; i = e[i].nxt) {
    int v = e[i].v;
    if (dfn[v] == 0) {
      ++cnt;
      Tarjan(v);
      low[u] = min(low[u], low[v]);
      if ((u == root && 1 < cnt) ||
          (u != root && dfn[u] <= low[v]))
        cut[u] = true;
    } else {
      low[u] = min(low[u], dfn[v]);
    }
  }
}

割边

定义

在一个无向图中,如果有一个边集,删除这个边集以后,图的连通分量增多,就称这个边集为割边集合。
如果某个割边集合只含有一条边 ( u , v ) (u,v) (u,v),那么 ( u , v ) (u,v) (u,v)称为一条割边。

判定

一条边 ( u , v ) (u,v) (u,v)为割边,当且仅当 ( u , v ) (u,v) (u,v)在搜索树中且 d f n ( u ) < l o w ( v ) dfn(u)<low(v) dfn(u)<low(v)

int from[N]; // 到达该结点通过的边的编号
bool cut[M << 1]; // 判断是否为割边

void Tarjan(int u) {
  static int now = 0;
  dfn[u] = low[u] = ++now;
  for (int i = G[u]; i != 0; i = e[i].nxt) {
    if (i == (from[u] ^ 1)) continue;
    int v = e[i].v;
    if (dfn[v] == 0) {
      from[v] = i;
      Tarjan(v);
      low[u] = min(low[u], low[v]);
      if (dfn[u] < low[v])
        cut[i] = cut[i ^ 1] = true;
    } else {
      low[u] = min(low[u], dfn[v]);
    }
  }
}

点双连通分量

定义

对于一个连通图,若任意两点之间至少存在两条“点不重复”的路径,则称该图是点双连通的。
对于一个图,点双连通的极大子图称为点双连通分量。

性质

不同的点双连通分量最多只有一个公共点,而这个公共点一定是割点。
任意的割点至少是两个不同点双连通分量的公共点。

求解

仿照求解强连通分量的方法,在dfs时维护一个栈,若当前边 ( u , v ) (u,v) (u,v)满足 d f n ( u ) ≤ l o w ( v ) dfn(u) \leq low(v) dfn(u)low(v)
说明 u u u为割点。将栈中的结点依次弹出,但 u u u不弹出。
注意割点属于多个点双连通分量。

void Tarjan(int u) {
  dfn[u] = low[u] = ++now;
  S[++top] = u;
  for (int i = G[u], cnt = 0; i != 0; i = e[i].nxt) {
    int v = e[i].v;
    if (dfn[v] == 0) {
      ++cnt;
      Tarjan(v);
      low[u] = min(low[u], low[v]);
      if ((u == root && cnt > 1) ||
          (u != root && dfn[u] <= low[v]))
        cut[u] = true;
      if (dfn[u] <= low[v]) {
        +vbcc_num;
        b[v_dcc].clear();
        do {
          vbcc[vbcc_num].push_back(S[top--]);
        } while (S[top + 1] != v);
        vbcc[vbcc_num].push_back(u);
      }
    } else {
      low[u] = min(low[u], dfn[v]);
    }
  }
}

边双连通分量

定义

对于一个连通图,若任意两点之间至少存在两条“边不重复”的路径,则称该图是边双连通的。
对于一个图,边双连通的极大子图称为边双连通分量。

性质

割边不属于任何一个边双连通分量。

求解

根据变双联通分量的定义,只需求一遍割边。然后删去割边,原图会分裂成若干个连通块,每个连通块就是一个边双连通分量。

DAG

定义

有向无环图。。。

拓扑排序

流程

  1. 扫描每个结点,吧入度为 0 0 0的结点加入队列。
  2. 从队列开始,每次取出队首元素,扫描其出边,将其到达的顶点入度减 1 1 1
  3. 重复 2 2 2,直到队列为空。
void Topological_Sort() {
  static queue<int> Q;
  for (int i = 1; i <= n; ++i)
    if (d[i] == 0) Q.push(i);
  while (!Q.empty()) {
    int u = Q.front(); Q.pop();
    for (int i = G[u]; i != 0; i = e[i].nxt) {
      int v = e[i].v;
      --d[v];
      if (d[v] == 0)
        Q.push(v);
    }
  }
}

应用

  1. 判断一张图是否为DAG。
  2. 在拓扑排序时DP。

DILWORTH 定理

在一个DAG上
最小反链覆盖等于最长链长度
最小链覆盖等于最长反链长度
反链:一个点集,没有任何一对点是前驱后继关系

你可能感兴趣的:(图论——连通性)