连通性相关
大量誊抄 2023 年集训队论文。轻喷。
一些定义、定理
基础知识。
点 / 边割集
对与 \(u,v\in V\),若 \(S\subseteq V\) 满足 \(u,v\not\in S\),且 \(G[V\setminus S]\) 中 \(u,v\) 不连通,那么称 \(S\) 是 \(u,v\) 的点割集。边割集类似定义。
对于连通图 \(G\),若 \(S\subseteq V\) 且 \(G[V\setminus S]\) 不连通,则 \(S\) 是 \(G\) 的一个点割集。边割集类似定义。
点 / 边连通度
对于 \(u,v\in V\),若其任意点割集大小不小于 \(s\),则称 \(u,v\) 是 \(s\text{-点连通}\) 的。若其任意边割集大小不小于 \(t\),则称其 \(t\text{-边连通}\)。
\(u,v\) 的点 / 边连通度是其最小点割集 / 边割集的大小(上述 \(s,t\) 的最大值)。\(G=(V,E)\) 的点 / 边连通度是最小点割集 / 边割集的大小(任意点对间连通度的最小值)。规定 \(K_n\) 的点连通度为 \(n-1\)。
由定义知,\(1\text{-点连通}\) 与 \(1\text{-边连通}\) 等价,等价于一般意义的连通。
发现点连通性与是否是简单图无关,而边连通性有关。
Menger 定理
-
\(u,v\) 是 \(k\text{-边连通的}\),当且仅当存在 \(k\) 条 \(u\rightsquigarrow v\) 的两两边不交的路径。
-
\(u,v\) 是 \(k\text{-点连通的}\),当且仅当存在 \(k\) 条 \(u\rightsquigarrow v\) 的除端点外两两点不交的路径。
这其实是最大流最小割定理的特化版本。
值得注意的是没有要求路径不同,于是 \(K_2\) 对于任意 \(k\) 是 \(k\text{-点连通}\) 的。
DFS 生成树
很多神秘的东西发现对应不到其它的算法时,就上最纯粹的 DFS 生成树的力量吧。
在有向图中,DFS 生成树有 \(4\) 种边:
-
树边:每次搜索找到一个还未访问过的节点时就形成了一条树边。
-
返祖边:搜索时遇到在树上的祖先节点,指向祖先的边。
-
横叉边:搜索时遇到已访问过的节点,但该节点不是当前节点的祖先,就形成了一条横叉边。
-
前向边:搜索时遇到了子树内的点,形成一条前向边。
无向图中除了树边就是非树边(同时也是返祖边)。
这是核心性质。很多图上构造题就是要用 DFS 生成树找性质。
性质
有向图 DFS 生成树
显然每条返祖边对应着一个环,但注意不是一一对应。
无向图 DFS 生成树
极其有用的一个性质是:无向图中,DFS 生成树上的返祖边构成的环是原图的环空间的一组基。这意味着 DFS 生成树上的返祖边构成的环异或起来属于原图中的环的集合的子集构成的簇。
还有 \(4\) 个性质。
-
祖先后代性:任意非树边两端有祖先后代关系。
-
子树独立性:一个结点的每个儿子的子树之间没有边。
-
时间戳区间性:子树内的时间戳是一段区间。
-
时间戳单调性:结点的时间戳小于其子树内结点的时间戳。
无向图
双连通分量
板子多,尽量理解记忆。
非常推荐 Alex_Wei 的 blog。
桥
定义在无向图上。
简化的定义:一张图 \(G\) 若在删去边 \(e\) 后不再连通,则称 \(e\) 为 \(G\) 的桥。
一种等价描述是,\(\{e\}\) 是某个点对的边割集。
-
割边一定在 DFS 生成树上。删去割边后,形成两个连通分量,分别是一棵子树和子树的补。
-
设 \(G\) 的割边集合为 \(E'\),\(G'=(V,E\setminus E')\),则对于任意 \(u,v\in V\),\(u,v\) 在 \(G\) 中边双连通当且仅当在 \(G'\) 中连通。
求边双的时候顺带就求出桥了。
边双
根据割边定义了边双连通关系。这是一种等价关系,\(G\) 中结点可以按照这个等价关系划分为若干等价类,每个等价类的导出子图称为一个边双连通分量。
由其定义,知存在性。
Tarjan 1
考虑边 \(e=(x,y)\),一条割边首先要是 DFS 生成树的树边。设 \(x\) 是 \(y\) 的儿子,那么 \(e\) 是割边当且仅当不存在 \(x\) 子树中的点(去掉 \(x\))到 \(x\) 的祖先的边。
于是可以树形 DP,设 \(low_x\) 表示,从 \(x\) 子树中的任意一个点出发,只能通过至多一条非树边能够到达的结点的最小 DFS 序。这里不能从树边走到 \(x\) 的父亲。
可以写出转移,\(low_x=\min\Big\{dfn_x,\min\limits_{z\in son_x} low_x,\min\limits_{(x,z)\text{is not tree edge}} dfn_z\Big\}\)
满足 \(low_x=dfn_x\) 的点就是一条割边的下端点,也是一个边双的最浅点。
发现这样的 \(x\) 时,其子树中未删除的部分就构成一个边双,弹栈删除这个子树即可。
代码
#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=5e5+10,maxm=2e6+10;
int n,m;
int tot=1,head[maxn<<1];
struct EDGE{
int v,nxt;
}e[maxm<<1];
inline void add(int u,int v){
e[++tot].v=v;e[tot].nxt=head[u];head[u]=tot;
}
int dfc,tp,stk[maxn],dfn[maxn],low[maxn];
vector< vector<int> > ans;
void tarjan(int u,int eid){
dfn[u]=low[u]=++dfc;stk[++tp]=u;
for(int i=head[u];i;i=e[i].nxt){
if(i==(eid^1)) continue;
int v=e[i].v;
if(!dfn[v]){
tarjan(v,i);low[u]=min(low[u],low[v]);
if(low[v]>dfn[u]){
vector<int> tmp;
for(int x=0;x!=v;) tmp.emplace_back(x=stk[tp--]);
ans.push_back(tmp);
}
}
else low[u]=min(low[u],dfn[v]);
}
if(!eid){
vector<int> tmp;
for(int x=0;x!=u;) tmp.emplace_back(x=stk[tp--]);
ans.push_back(tmp);
}
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;++i){
int u,v;cin>>u>>v;
add(u,v);add(v,u);
}
for(int i=1;i<=n;++i){
if(!dfn[i]) tarjan(i,0);
}
cout<<(int)ans.size()<<'\n';
for(vector<int> ecc:ans){
cout<<(int)ecc.size()<<' ';
for(int x:ecc) cout<<x<<' ';
cout<<'\n';
}
return 0;
}
Tarjan 2
可以把所有桥 Tarjan 出来,然后求边双。
这是根据定义做的。其实不如顺带求边双,太废物了这个算法,被彻底偏序。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+5,maxm=4e6+5;
int n,m,tot=1,dfc=0,ans=0,head[maxn],dfn[maxn],low[maxn];
bool bri[maxm],used[maxn];
vector<int> d[maxn];
struct edge{
int v,nxt;
}e[maxm];
inline void add(int u,int v){
e[++tot].v=v;
e[tot].nxt=head[u];
head[u]=tot;
}
void tarjan(int u,int f){
dfn[u]=low[u]=++dfc;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v]){
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u]) bri[i]=bri[i^1]=true;
}
else if(v!=f){
low[u]=min(low[u],dfn[v]);
}
}
}
void dfs(int u){
d[ans].push_back(u);
used[u]=true;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(bri[i]||used[v]) continue;
dfs(v);
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,u,v;i<=m;++i){
scanf("%d%d",&u,&v);
if(u==v) continue;
add(u,v);
add(v,u);
}
for(int i=1;i<=n;++i){
if(!dfn[i]){
tarjan(i,0);
}
}
for(int i=1;i<=n;++i){
if(!used[i]){
ans++;
dfs(i);
}
}
printf("%d\n",ans);
for(int i=1;i<=ans;++i){
int len=d[i].size();
printf("%d ",len);
for(int j=0;j<len;++j){
printf("%d ",d[i][j]);
}
printf("\n");
}
return 0;
}
边双缩点
直接缩就好,因为一个点只会在一个点双中,缩完后会形成一棵树,树边对应原图中的割边。
割点
定义在无向图上。
对于无向连通图 \(G=(V,E)\),若 \(u\in V\) 满足 \(G[V\setminus \{u\}]\) 不再连通,则称 \(u\) 是一个割点。
对于不一定连通的无向图 \(G\),任意一个连通分量的割点都称为其割点。
等价的描述:若 \(\{u\}\) 是某一对点的点割集,则 \(u\) 是割点。
使用 Tarjan 中的 \(low_x\):DFS 树上 \(x\) 子树内任意一点出发,经过至多一条非树边能够到达结点的最小 DFS 序。
\(x\) 是割点当且仅当至少满足下述两个条件之一:
-
\(x\) 是 DFS 树的根结点且有至少两个子结点。
-
\(x\) 是 DFS 树的非根结点,且存在一个子结点满足 \(low_y\ge dfn_x\)。
第一条显然。证明第二个。
若存在这样的 \(x,y\),那么显然删去 \(x\) 后 \(y\) 子树形成单独的一个连通分量,不与外界连通,故 \(x\) 是割点。
若 \(x\) 是割点,考虑删去 \(x\) 后至少一个儿子 \(y\) 与 \(x\) 子树的补不连通,这就表明 \(low_y\ge dfn_x\)。
板子
void tarjan(int u){
dfn[u]=low[u]=++dfc;
int ch=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v]){
ch++;
tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]&&u!=rt) ans+=!iscut[u],iscut[u]=true;
}
else low[u]=min(low[u],dfn[v]);
}
if(ch>=2&&u==rt) ans+=!iscut[u],iscut[u]=true;
}
点双
以下在无自环的情况下讨论。显然自环没啥用,直接去掉。
对于无向图 \(G\),若 \(V'\subseteq V\) 的导出子图 \(G[V']\) 点双连通,且对于任何 \(V'\subset V''\subseteq V\),\(G[V'']\) 不点双连通,则称 \(G'\) 是 \(G\) 的一个点双连通分量。
可以类似边双求解。先求 \(low\),若对于一对父子 \((x,y)\) 满足 \(low_y\ge dfn_x\),则取出 \(y\) 中未被删除的部分,与 \(x\) 组成一个点双,然后把 \(y\) 子树内未被删除的部分删除。
值得注意的是,一个割点会在多个点双中。
显然点双对于点并不是等价关系。边双部分,我们可以利用边双是等价关系而通过划分 \(V\) 来定义边双。点双应当也有类似的形式,我们可以从划分边集的视角来刻画点双。
-
对于点双中任意不同两点 \(u,v\),存在经过 \(u,v\) 的简单环。这是 Menger 定理的直接推论。
-
对于点双中任意一个点 \(u\) 和一条边 \(e\),存在经过 \(u,e\) 的简单环;对于两条边 \(e_1,e_2\),存在经过 \(e_1,e_2\) 的简单环。
证明一下。将一条边拆成 \(x\to z\to y\),新图仍点双连通。此时边变成点,同上个定理。
-
无向图上的两条边 \(e_1,e_2\),若存在一个简单环同时包含 \(e_1,e_2\),则称之共环。
-
可以知道共环是等价关系,点双内的边是共环意义下的一个等价类。
证明。首先同一点双内的边共环,上方引理已证。
对于不同点双中的边,设存在一个简单环同时包含这两条边,那么这个环上的点一定点双连通,这与两条边属于不同点双连通分量矛盾。
一个基本事实:一条边只在一个点双连通分量中。
于是边双是对点集的划分,点双是对边集的划分。
板子
#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=5e5+10,maxm=2e6+10;
int n,m;
int tot,head[maxn];
struct EDGE{
int v,nxt;
}e[maxm<<1];
inline void add(int u,int v){
e[++tot].v=v;e[tot].nxt=head[u];head[u]=tot;
}
int dfc,tp,stk[maxn],dfn[maxn],low[maxn];
vector< vector<int> > ans;
void tarjan(int u){
dfn[u]=low[u]=++dfc;stk[++tp]=u;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v]){
tarjan(v);low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]){
vector<int> tmp={u};
for(int x=0;x!=v;) tmp.emplace_back(x=stk[tp--]);
ans.emplace_back(tmp);
}
}
else low[u]=min(low[u],dfn[v]);
}
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;++i){
int u,v;cin>>u>>v;
if(u==v) continue;
add(u,v);add(v,u);
}
for(int i=1;i<=n;++i){
if(!head[i]) ans.emplace_back(vector<int>{i});
else tarjan(i);
}
cout<<(int)ans.size()<<'\n';
for(vector<int> vcc:ans){
cout<<(int)vcc.size()<<' ';
for(int x:vcc) cout<<x<<' ';
cout<<'\n';
}
return 0;
}
圆方树
可以用圆方树描述点双的结构。
对于点数不少于 \(2\) 的无向连通图 \(G\),建立一张新的无向图 \(T\),每个结点对应 \(G\) 的一个结点或点双连通分量。两个结点之间有边当且仅当一个是 \(G\) 中的结点 \(x\),一个是点双连通分量 \(G[V']\),且 \(x\in V'\)。
称 \(T\) 是 \(G\) 的圆方树,对应 \(G\) 中结点的结点称为圆点,对应 \(G\) 中点双连通分量的结点称为方点。
考虑原图上一条长度为 \(k\) 的路径 \(u\rightsquigarrow v\),它依次经过每条边所属的点双连通分量,于是对应圆方树上长度为 \(2k\) 的路径,因为经过了方点。
因此连通图的圆方树连通。
同时,圆方树无环,否则还能缩点双连通分量。
所以圆方树一定是树。
-
圆方树是二分图,圆点和方点构成两部。
-
一个点是割点当且仅当其在圆方树上对应的圆点度数大于 \(1\)。圆方树的叶子(包括度数为 \(1\) 的根) 必定是圆点,并且是全部非割点。
-
一张点数不少于 \(2\) 的无向图的全部点双连通分量的点数之和不超过 \(2n-2\)。
证明。在圆方树上选取一个圆点为根,然后任意给每个方点指定一个儿子。这些儿子一定是圆点且不重复,因此方点数量不超过非根圆点的数量,即 \(n-1\)。
因此圆方树总点数不超过 \(2n-1\),总边数不超过 \(2n-2\)。而每个点双连通分量大小之和等于方点的度数之和等于总边数,得证。
-
无向连通图 \(G\) 上两个结点 \(u,v\) 之间的任何简单路径,一定经过且只经过它们圆方树路径上所有方点对应的点双中的边,一定经过但是不一定只经过圆方树路径上的圆点。
-
仙人掌:每条边在至多一个简单环上的无向连通图叫边仙人掌。
考虑对仙人掌进行 Tarjan,得到的 DFS 生成树上,反祖边对应的树上路径两两边不交。于是 Tarjan 直接可以找到所有环。这个结构就很清晰,方便进行 DP 之类的。
仙人掌上的路径信息可以转化到圆方树上解决。
无向图耳分解
小清新简单知识点。帮助深度理解 DFS 树。
一种图上状压 DP 的方式大概是逐点构成耳,然后合并到点集中,这样可以保证 DP 出来是点双 / 边双 / 强连通图。有点像排列拆置换环(?)
定义
定义耳:在无向图上,简单路径或者简单环 \(P:v_1\to v_2\to \cdots \to v_k\) 被称为子图 \(G'=(V',E')\subseteq G\) 的耳,如果 \(v_1,v_k\in V'\) 且 \(v_2,\cdots,v_k\not\in V'\)。类似地可以定义有向图中的耳,只需使简单路径或者简单环中的边定向即可。
定义开耳:在耳的基础上,若 \(P\) 是简单路径,即 \(v_1\ne v_k\),则称 \(P\) 为开耳。有向图中的定义类似。
定义耳分解:将无向图 \(G\) 划分为若干耳的方案称为耳分解。具体地,耳分解是一系列 \(G\) 的子图 \(G_0,G_1,\cdots,G_k\),满足 \(G_{i-1}\) 添加一个耳后得到 \(G_i\),且 \(G_0\) 是一个点或者一个环。若每次添加的耳都是开耳,则称之开耳分解。开耳分解中,\(G_0\) 可以是简单环。有向图上定义类似。
定义双极定向:给定无向图 \(G\) 和 \(s,t\) 两个顶点,将 \(G\) 中无向边定向使之成为 DAG,且 \(s\) 是唯一入度为 \(0\) 的点,\(t\) 是唯一出度为 \(0\) 的点的方案称之双极定向。
耳分解的性质之类的
我们想要知道什么样的无向图存在耳分解。如果一张图存在耳分解,则每条边恰好属于一个耳,这是显然的。
耳是简单路径或者简单环,联想到双连通性。
而有向图呢?自然是强连通性。
边双连通图
考虑割边,发现割边不属于任意简单环,且包含割边的简单路径的端点一定在割边两侧,于是割边不属于任意一个耳,所以非边双无耳分解。
在边双连通图上,对每条边都存在包含这条边的简单环。
容易构造性地证明,边双有耳分解:
每次选择一条连接 \(V'\) 和 \(V\setminus V'\) 的边 \((u,v)\)。找到包含这条边的简单环,从 \(u\) 出发再回到 \(V'\) 的路径就是一个耳。
这种方法可以证明,但是构造起来太麻烦。可以给出更加简单的构造。
图论中的构造题,直接考虑 DFS 树。
构造耳分解方案
先求出以 \(1\) 为根的 DFS 生成树。
找到以 \(1\) 为端点的非树边 \(1\to x\),令 \(G_0\) 为树上路径加上 \(1\to x\) 构成的简单环。
若现已生成 \(G_i\),找到一个不属于 \(G_i\) 的点 \(x\) 满足其父亲 \(y\) 属于 \(G_i\)。由于边双连通,必然有 \(x\) 子树中的点连向 \(y\) 祖先的边,抽出这样的环加入,构成 \(G_{i+1}\)。
直到 \(G_i\) 的点集为 \(V\),若还有剩余的边,则依次作为一个耳加入。
由于 \(G_i\) 总是包含 \(1\) 的连通块,所以总能找到 \(x\)。
点双连通图
设点数 \(n\ge 3\),此时易证点双同时也是边双,因为假设点双中有割边,那么必有割点。于是点双必有耳分解。
继续加强一下结论。设点双中没有自环,我们可以构造性地证明,\(n\ge 3\) 的无自环点双必有开耳分解:
从任意简单环开始,设 \(s,t\in V',e\in E\setminus E',s\ne t\)。在点双中必然可以找到以 \(s,t\) 为两端且包含 \(e\) 的路径,从 \(e\) 向前向后分别找到第一个在 \(V'\) 中的点 \(s'\) 和 \(t'\),这就得到了一个包含 \(e\) 的开耳。
相反,可以证明,有割点的图没有开耳分解。
构造开耳分解方案
先求出以 \(1\) 为根的 DFS 生成树。
找到一条以 \(1\) 为端点的非树边 \(1\to x\),令 \(G_0\) 为树上路径加上 \(1\to x\) 构成的简单环。
若现已生成 \(G_i\),如果 \(G_i\) 点集不为 \(V\),找到一个不属于 \(G_i\) 的点 \(x\) 满足其父亲 \(y\) 属于 \(G_i\),由点双连通知存在从 \(x\) 的子树连向 \(y\) 的父亲的祖先的非树边。抽出这个环加入,构成 \(G_{i+1}\)。显然这是一个开耳。
最后剩余的边各自作为一个开耳加入。这里用到无自环条件。
双极定向问题
给定无向图 \(G=(V,E)\),和不同的两个结点 \(s,t\),可以说明下面四个命题等价:
-
在添加无向边 \((s,t)\) 后,\(G\) 点双连通。
-
\(G\) 的圆方树中,所有的方点构成一条链,且 \(s\rightsquigarrow t\) 是圆方树的一条直径。
-
存在一种对 \(G\) 的边进行定向的方法,得到一个 DAG,且 \(s\) 入度为 \(0\),\(t\) 出度为 \(0\),其余点的出度入度都不为 \(0\)。
-
存在一个所有点的排列 \(p_i\),使得 \(p_1=s,p_n=t\),且任意前缀以及任意后缀的导出子图都是连通的。
若 \(s,t\) 在 \(G\) 上不连通,那么显然全假。以下假设连通。
第 \(1,2\) 个命题等价是显然的。
考虑第 \(3,4\) 个命题。
若命题 \(3\) 成立,则取出一个拓扑序,其满足命题 \(4\)。若命题 \(4\) 成立,则找到这个排列,将边定向为排列中前面的点连向后面的点,满足命题 \(3\)。
于是第 \(3,4\) 个命题等价。
由命题 \(1\) 推命题 \(3\)。连接 \((s,t)\) 后由于点双连通,可以求出一个开耳分解,且 \(G_0\) 包含边 \(s\to t\)。由于 \(G_0\) 是一个简单环,显然存在定向方案。
考虑归纳,设 \(G_i\) 已经定向完毕,现在加入一个开耳得到 \(G_{i+1}\),设耳的两个端点分别为 \(u,v\),若在 \(G_i\) 中 \(u\) 可达 \(v\),则耳的方向定向为 \(u\to v\),否则定向为 \(v\to u\)。
每个点在加入时都拥有了 \(1\) 的入度和出度,除了 \(s,t\),所以最终的定向方案满足命题 \(3\)。
由命题 \(4\) 推 \(1\)。只需证非 \(1\) 推出 非 \(4\) 。
连上 \((s,t)\),设 \(1\) 不成立,那么由于 \(s,t\) 之间连出了简单环,所以 \(s,t\) 必然不是割点,于是存在一个割点 \(u\),使得删去 \(u\) 后得到包含 \(s,t\) 的连通块和其它若干连通块。设 \(S\) 是删去 \(u\) 后不包含 \(s,t\) 的一个连通块,考察 \(S\cup\{u\}\) 中的点在命题 \(4\) 的排列中最早和最晚出现的点 \(x\) 和 \(y\)。这两个点中至少一个点不为 \(u\)。
若 \(x\) 不为 \(u\),则 \(p_1=s,p_2,\cdots ,p_i=x\) 这个前缀中尚未出现 \(u\),所以 \(s,x\) 不连通,与命题 \(4\) 矛盾。
若 \(y\) 不为 \(u\),类似考虑后缀即可。
如何构造方案?直接按照上述过程构造比较麻烦。可以在 DFS 生成树上考虑。
可以转化到命题 \(4\)。相当于初始全是白点,不断把点染黑,使得同一时刻黑点的导出子图连通,白点的导出子图连通。
以 \(s\) 为根,考虑剥叶子。对于一个叶子 \(u\),如果 \(low_u\) 或者 \(fa_u\) 其中之一被染黑(这里 \(low_u\) 的定义是子树内经过至多一条非树边能到达的最浅的点),立即染黑 \(u\) 不会影响正确性。于是对每个点搞一个列表,在一个点的 \(low_u\) 和 \(fa_u\) 处插入 \(u\)。染黑一个点后进入其列表递归染黑即可。
由于叶子一定比非叶子先染黑,正确性有所保证。
注意,剥叶子剥到 \(t\) 时不再继续,因为要保留 \(t\) 到最后剥。最终只会留下 \(s\rightsquigarrow t\) 上的点,最后再从 \(s\) 一路染到 \(t\) 即可。
可以断言,染到 \(t\) 时其列表中的点都已经被染色了,否则 \(t\) 是一个割点。
实现可以首先抽出 \(s\rightsquigarrow t\) 的路径染色,强制其不被其他点触发删除。然后依次手动删掉并进行上面的递归操作。
割集与切边等价
割空间与环空间
这里的割集与前面的点割集和边割集不太一样。看定义:
在无向图 \(G=(V,E)\) 中,对于 \(V\) 的一个二划分 \(V=V_1 \uplus V_2\),定义 \(V_1,V_2\) 间的割集为 \(\left\{e\in E\mid e=(x,y),x\in V_1,y\in V_2\right\}\),记为 \(C(V_1,V_2)\)。
若 \(G\) 不连通,那么其割集可以拆分为各个连通分量的割集的并,所以只考虑连通图的割集。
树是最简单的连通图,从树的割集出发:
- 对于树 \(T=(V,E)\),任意边的子集 \(E'\subseteq E\) 都是割集,且对应的划分唯一。
证明。删去 \(E'\) 后 \(T\) 将分裂为若干连通分量,设 \(C\) 为这些连通分量的集合,\(E'\) 将把 \(C\) 中的元素连成树 \(T'\)。于是容易说明唯一的二划分就是 \(T'\) 按深度奇偶分层的划分。
- 若连通图 \(G=(V,E)\) 有生成树 \(T=(V,E_0)\),对于任意 \(E'_0\subseteq E_0\),\(G\) 存在唯一的割集 \(E'\) 满足 \(E'\cap E_0=E'_0\),且对应的划分 \(V_1,V_2\) 唯一。
由上一条可以直接从 \(E'_0\) 得到唯一的划分,对于剩下的边,若两端在同一集合内边不属于 \(E'\),否则属于 \(E'\)。显然唯一。
还能进一步分析 \(E'\) 中的非树边。若非树边 \(e=(x,y)\) 对应的树上路径 \(x\rightsquigarrow y\) 上有奇数条边在 \(E'_0\) 中,那么 \(x,y\) 在 \(T'\) 上深度奇偶性不同,于是 \(e\in E'\),否则 \(e\notin E'\)。
可以研究所有割集的集合的性质,引出了割空间。
- 割空间:在无向图 \(G=(V,E)\) 中,将任何一个边的集合看成 \(\mathbb F_2\) 上的 \(m=|E|\) 维向量,则所有割集组成的集合是 \(\mathbb F_2\) 上的线性空间,称为割空间。其维数为 \(n-c\),\(c\) 为 \(G\) 的连通分量个数。
线性代数学得不行。急眼了。
有向图
在有向图中,若存在路径 \(x\rightsquigarrow y\),则称 \(x\) 可达 \(y\)。
将一个有向图中的有向边变成无向边,得到的无向图称为原来有向图的基图。若基图连通,称原图弱连通。
对于 DAG,定义拓扑排序:拓扑排序是一个点的排列。若 \(x\) 可达 \(y\),则 \(x\) 在拓扑排序中排在 \(y\) 之前。
可达性问题
静态传递闭包
稠密图上的 Floyd 算法是熟知的。
稀疏图上可以枚举终点,然后在反图上搜索,时间复杂度 \(O(nm)\)。
可以 bitset 优化。先缩点成为 DAG,再使用 bitset,设 \(f_{x,y}\) 表示 \(x\) 是否可达 \(y\),可以按逆拓扑序用 bitset 运算得到。时间复杂度 \(O(\dfrac{nm}{w})\)。
动态可达性
一般比较困难。需要沿用 bitset 方法,结合序列分块,操作分块等数据结构技巧。
不太好,期待分块大神教教。
强连通分量
这是定义在有向图上的。
强连通分量(Strongly Connected Components,SCC):极大的强连通子图。
在有向图 \(G\) 中,若两个结点 \(u,v\) 满足 \(u\) 可达 \(v\) 且 \(v\) 可达 \(u\),则称 \(u,v\) 强连通。若 \(G\) 中任意两点强连通,则称 \(G\) 强连通。
易知强连通性是一种点集上的等价关系,在有向图 \(G\) 中可以将所有点按强连通关系划分为等价类。
每个等价类的导出子图称为 \(G\) 的一个强连通分量。
有向图 DFS 生成树
回忆一下其性质。
与无向图 DFS 生成树最大的不同在于,非树边有:
-
后代连向祖先的返祖边。
-
祖先连向后代的前向边。
-
端点没有祖先后代关系,DFS 序大的点连向 DFS 序小的点的横叉边。
这说明非树边的结构比无向图更复杂一点。
还能注意到,返祖边和横叉边可以减小时间戳,树边、前向边增大时间戳。
前向边不影响连通性。所以去掉前向边是不影响强连通分量的。
求解强连通分量
类似边双,记录 \(low_x\) 表示从 \(x\) 子树内任意一个点出发,经过至多一条非树边能够到达的结点的最小 DFS 序。
如果经过的非树边是返祖边,那么会形成一个环,这上面的点都强连通。前向边没有影响。
仅考察这两条边时,类似求解边双时 Tarjan 求出 \(dfn\) 和 \(low\),若 \(low_x=dfn_x\),那么 \(x\) 是其所在强连通分量中最浅的那个点,不断弹栈即可得到这个强连通分量。
由于存在横叉边,上述算法不完全正确。考察一条横叉边 \(u\to v\),可知不存在从 \(v\) 子树连向 \(u\) 子树的横叉边,这是横叉边的性质保证的。于是 \(v\) 可达 \(u\) 当且仅当 \(v\) 可达 \(w=\text{LCA}(u,v)\)。
若 \(v\) 不可达 \(w\),那么它必然在 \(w\rightsquigarrow v\) 上的某一点处(不含 \(w\))被计入强连通分量。
若 \(v\) 可达 \(w\),那么 \(v\) 必然在 \(rt\rightsquigarrow w\) 上的某一点处被计入强连通分量。而此时遍历到 \(u\),必然尚未被计入某个强连通分量。
于是可知,如果横叉边的另一端 \(v\) 还未被划分到一个强连通分量中,那么用 \(dfn_v\) 更新 \(low_u\),这样 \(w\rightsquigarrow u\) 路径上任意一个点(除了 \(w\))都不会被判定是所在强连通分量中最浅的那个点,而事实如此。否则这条横叉边没有用,不会产生新的强连通分量,不管。
整个算法只需在求解边双的 Tarjan 上加入对横叉边的特判就行。使用一个数组记录一个点是否已经被划分到一个强连通分量中,这等价于是否在栈中。
板子
void tarjan(int u){
dfn[u]=low[u]=++dfc;stk[++tp]=u;instk[u]=true;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v]){
tarjan(v);low[u]=min(low[u],low[v]);
}
else if(instk[v]) low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
num++;
do{
scc[num].emplace_back(stk[tp]);bel[stk[tp]]=num;
instk[stk[tp]]=false;
}while(stk[tp--]!=u);
}
}
时间复杂度 \(O(n+m)\)。
遍历每个点,如果当前点 \(u\) 还未被遍历过,就继续搜索 \(u\)。这一步是防止有些点不能一次遍历到。
SCC 缩点
强连通分量可以进行缩点,缩点后形成一张 DAG。在这张 DAG 上可以进行更多操作。
分量编号与拓扑序的关系
可以观察到在 Tarjan 的过程中,我们首先找到的 SCC 是没有出边的那些结点构成的 SCC。于是分量编号实际上满足某种逆拓扑序。
可以说明,在 SCC 缩点后的 DAG 上,分量编号满足 DAG 的一个逆拓扑序。
有向图耳分解
类似的定义。
在有向图 \(G=(V,E)\) 中有子图 \(G'=(V',E')\),若简单路径或简单环 \(P:x_1\to x_2\to \cdots \to x_k\) 满足 \(x_1,x_k\in V'\)
- 有向图 \(G\) 可耳分解,当且仅当 \(G\) 强连通。
证明。必要性,若有耳分解 \((G_0,G_1,\cdots G_k)\),则简单环 \(G_0\) 强连通。若 \(G_i\) 强连通,易知加入耳后仍强连通,于是归纳得 \(G\) 强连通。
充分性,若 \(G\) 强连通,可以构造一组耳分解。
首先求出以 \(1\) 为根的一棵 DFS 生成树,用 Tarjan 求出 \(low\)。由 \(G\) 强连通可知,\(x\ne 1\) 时 \(low_x<dfn_x\)。
令 \(G_0\) 为只包含 \(1\) 的图,然后按 DFS 序从小到大枚举每个点,设当前点 \(x\) 的 DFS 序为 \(p\),则 DFS 序为 \(1\cdots p-1\) 的点都在当前 \(G_i\) 中。若 \(x\) 在当前 \(G_i\) 中直接跳过,否则由 \(low_x<dfn_x\) 可知其存在后代 \(y\) 存在边 \(y\to z\),\(z\) 在 \(G_i\) 中,于是将 \(fa_x\to x\rightsquigarrow y\to z\) 作为一个耳加入 \(G_i\)。
加完所有的点后,再将剩下的边单独作为耳加入即可。
- 无向图 \(G\) 可以被定向为强连通图,当且仅当 \(G\) 边双连通。
证明。必要性,若 \(G\) 不是边双连通的,如果不连通,显然不存在方案;如果存在割边 \((x,y)\),无论这条边如何定向,总有 \(x\) 不可达 \(y\) 或者 \(y\) 不可达 \(x\) 二者之一成立,不存在方案。
充分性,若 \(G\) 边双连通,则存在耳分解。只需将 \(G_0\) 定向为有向环,然后其他的耳依次定向即可。
真正构造定向方案时,只需在 DFS 生成树上将树边定向为父亲到儿子,非树边定向为后代向祖先即可。这与耳分解构造的定向基本相同。
-
混合图 \(G\) 可以被定向为强连通图,当且仅当以下两个条件同时满足:
-
将有向边看作无向边时,图边双连通。
-
将无向边拆成一对反向的有向边时,图强连通。
-
必要性显然(不满足二者之一必然不能定向为强连通图),下证充分。
若条件 \(1,2\) 同时满足,如果没有无向边,那就不用定向了,已经是强连通图。否则考虑取出一条无向边 \((u,v)\),证明存在一种给它定向的方法,使得定向后条件 \(2\) 仍然满足(条件 \(1\) 与无向边无关,总是满足的)。
记当前图为 \(G_0\),删去 \((u,v)\) 后的图为 \(G'_0\)。接下来把两张图中的无向边都拆成一对反向的有向边考虑。
由归纳假设,\(G_0\) 现在强连通,于是对于任意的 \(x\),都存在 \(x\rightsquigarrow u\) 的简单路径。现在只考察这条路径,若经过 \((u,v)\),在 \(G'_0\) 中 \(x\) 可达 \(v\),否则 \(x\) 可达 \(u\)。即在 \(G_0\) 中,\(x\) 可达 \(u\) 与 \(x\) 可达 \(v\) 至少一个成立。
同理可以说明 \(u\) 可达 \(x\) 与 \(v\) 可达 \(x\) 至少一个成立。
如果存在 \(x\) 满足 \(u\) 可达 \(x\) 且 \(x\) 可达 \(v\),或者 \(v\) 可达 \(x\) 且 \(x\) 可达 \(u\),那么在新图上 \(u\) 可达 \(v\) 或者 \(v\) 可达 \(u\),\((u,v)\) 这条边反着定向即可。
否则对于任意 \(x\),\(u,x\) 相互可达与 \(v,x\) 相互可达恰好一个成立。那么图中至多存在两个连通分量。如果 \(u,v\) 相互不可达,由可达关系的传递性可知,图中有两个连通分量。但这意味着无向边 \((u,v)\) 是割边,与条件 \(1\) 矛盾。因此 \(u\) 可达 \(v\) 与 \(v\) 可达 \(u\) 至少一个成立。还是反着定向即可。
这样定向后 \(u,v\) 相互可达,条件 \(2\) 仍然满足。
有向环
竞赛图
对于任意一对不同的结点 \(u,v\),\(u\to v\) 和 \(v\to u\) 之中恰好有一条边存在的简单有向图。
哈密顿路
图 \(G\) 中,经过所有点恰好一次的路径称为哈密顿路,若最后一个点可以一步回到起点,称这样的回路为哈密顿回路。
- 竞赛图有哈密顿路
对点数归纳。\(n=1\) 时平凡。\(n=2\) 时 成立。当 \(n\ge 3\) 时,若 \(n-1\) 个点的竞赛图有哈密顿路,不妨设前 \(n-1\) 个点的哈密顿路就是 \(1\to 2\to \cdots\to n-1\)。
-
若有边 \(n\to 1\),则放到开头。
-
若有边 \(n-1\to n\),则放到结尾。
-
若同时有边 \(1\to n,n\to n-1\),考虑 \([1,n-1]\) 每个点与 \(n\) 的连边,必然存在 \(x\in [1,n-2]\) 使得 \(x\to n,n\to x+1\)(这个可以二分找到最小的 \(t\) 使得存在 \(n\to t\),就找到了可以插入的地方)。
归纳得证。
- 强连通的竞赛图有哈密顿回路
首先取出一条哈密顿路,不妨是 \(1\to 2\to \cdots \to n\)。然后找到最大的 \(t\) 使得存在边 \(1\to t\)。这意味着 \(1\to t+1,\cdots ,1\to n\) 存在。
现在就有了一个环 \(1\to \cdots \to t\)。需要证明,这种情形下存在一个不在环上的点可以加入环。
由强连通性,存在不在环上的 \(x\) 与环上的点 \(y\) 存在 \(x\to y\),且存在 \(1\to x\)。于是环上插入的情形与上一个证明中在链中插入的情形相似。
强连通分量
- 竞赛图缩点后形成一条链
证明。对于不同两个强连通分量中的点 \(x,y\),一定存在 \(x\to y\) 或者 \(y\to x\),因此 \(x\) 可达 \(y\) 或者 \(y\) 可达 \(x\)。这样的可达关系形成全序,所以形成一条链。
求强连通分量不一定要 Tarjan,有一些更简单的方法。
首先找到竞赛图的一个哈密顿路,不妨设 \(1\to \cdots \to n\),这个可以在 \(O(n\log n)\) 的时间内搞出来。然后显然一个强连通分量是一段连续区间。对于一条边 \(x\to y\),若 \(x<y\),则不影响连通性。如果 \(x>y\),那么这段区间都处于同一强连通分量中,并查集合并起来即可。
最后可以得到 \([1,n]\) 的一个区间划分,这些区间就是所有的强连通分量。
不太实用。强大的 Landau 定理更有性价比。
- Landau 定理:设竞赛图 \(G\) 中所有点按照出度从小到大排列为 \(p_1,p_2,\cdots,p_n\),\(p_i\) 的出度为 \(d_i\),则对于每个 \(1\le k\le n\),有 \(\sum\limits_{i=1}^k d_i\ge \binom{k}{2}\)。
扩展:\(G\) 的每个强连通分量是 \(p\) 中的一个连续段,且 \(p_k\) 是一个强连通分量的右端点的充要条件是 \(\sum\limits_{i=1}^k d_i=\binom{k}{2}\)。
证明。仅考虑前 \(k\) 个点的导出子图,\(\binom{k}{2}\) 条边已经贡献了 \(\binom{k}{2}\) 的出度,于是 \(\sum\limits_{i=1}^k d_i\ge \binom{k}{2}\)。
若 \(x,y\) 属于不同的强连通分量,且 \(x\) 可达 \(y\)。那么对于所有 \(y\) 的出边 \(y\to z\),必然不存在 \(z\to x\),故 \(x\to z\) 存在,且 \(x\to y\) 存在,所以 \(d_x>d_y\)。再按逆拓扑序考虑,可以说明每个强连通分量确实是排列 \(p\) 中连续的一段。
若取到等号,说明 \(p_{k+1},\cdots,p_n\) 到 \(p_1,\cdots,p_k\) 没有边,所以两侧不可能属于同一强连通分量,因此 \(p_k\) 是一个右端点。反之,若 \(p_1,\cdots,p_k\) 构成一个强连通分量,类似可以说明取到等号。得证。
Landau 定理中的出度改换成入读,相应结论是类似成立的。
将所有点按入度从小到大排序,按照 \(\sum\limits_{i=1}^k d_i=\binom{k}{2}\) 的 \(k\) 为右端点,划分出来的每个连续UAN就是所有的强连通分量,且靠左的强连通分量可达靠右的强连通分量。
虽然单次求解的时间复杂度劣于 Tarjan,但是在某些方面更有优势。比如显然比 Tarjan 更支持动态操作。