进阶图论
进阶图论
I. 割点与桥
首先,我们得了解割点的含义
割点
对于一个无向图,如果把一个点删除后这个图的极大连通分量数增加了,那么这个点就是这个图的割点(又称割顶)。
通俗点说,就是连接两个或多个连通分量的公共点。
如何求割点呢,这里引用一个算法:Ttarjan
定义 dfn[i] 为 i 点DFS访问的顺序 , low[i] 为 i 点不经过其父亲能到达的最小的DFS序。
接着,我们开始DFS。
怎样判断割点呢, 对于一个顶点u,如果存在至少一个顶点 v (u的儿子),使得 low[v] > low[u] ,即不能回到祖先,那么u点为割点。(为什么自己想)
当然,此根据不适用搜索的起始点。(也是自己想)
更新low的代码:
if(!dfn[v])
low[u] = min(low[u], low[v]);
else if(instack[i])
low[u] = min(low[u], dfn[v]);
instack[i] 表示 i 点有没有被处理。u 是当前节点, v 是这个节点的所有邻居。
最后就是整体代码。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
int n, m;
vector<int>g[N];
int dfn[N], low[N], id;
//low,i;i不经父亲能达到的最小节点
stack<int>s;
int inst[N], cnt[N];
void tarjan(int u, int root){
dfn[u] = low[u] = ++id;
s.push(u);
inst[u] = 1;
int child = 0;
for(auto v : g[u]){
if(!dfn[v]){//未访问
tarjan(v, root);
low[u] = min(low[u], low[v]);
if(low[v] >= dfn[u] && u != root){
cnt[u] = 1;
//如果儿子的low值大于等于dfn,则代表其为儿子与其他非子树点相连的唯一途径
}
if(u == root)
child++;
}
else
low[u] = min(low[u], dfn[v]);
}
if(child >= 2 && u == root)
cnt[u] = 1;
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= m; i++){
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
for(int i = 1; i <= n; i++){
if(!dfn[i])
tarjan(i, i);
}
int ans = 0;
for(int i = 1; i <= n; i++)
if(cnt[i])
ans++;
cout<<ans<<"\n";
for(int i = 1; i <= n; i++)
if(cnt[i])
cout<<i<<" ";
return 0;
}
割边(无重边)
和割点差不多,叫做桥(点换成边嘛)。不做解释。
更新的方法和割点差不多, 只需改一处low[v] > dfn[u],也就是不用判断根节点了。
下面代码实现了对 无重边 的无向图求割边,其中,当 isbridge[x] 为真时,(father[x],x) 为一条割边。
int low[MAXN], dfn[MAXN], idx;
bool isbridge[MAXN];
vector<int> G[MAXN];
int cnt_bridge;
int father[MAXN];
void tarjan(int u, int fa) {
father[u] = fa;
low[u] = dfn[u] = ++idx;
for (const auto &v : G[u]) {
if (!dfn[v]) {
tarjan(v, u);
low[u] = min(low[u], low[v]);
if (low[v] > dfn[u]) {
isbridge[v] = true;
++cnt_bridge;
}
} else if (v != fa) {
low[u] = min(low[u], dfn[v]);
}
}
}
en就这么多。
圆方树
点双联通分量不是很好缩点,但是我们可以建立圆方树:每个点双建一个点,称之为方点,原图中每个点也建一个新点,称之为原点。每个方点与属于他的圆点连边,就构成了一棵树,称为圆方树。
图例
下面的图显示了一张图对应的点双和圆方树形态
因为圆方树是基于割点的,所以只需要用类似求割点的方法构建圆方树。
void Tarjan(int u) {
low[u] = dfn[u] = ++dfc; // low 初始化为当前节点 dfn
for (int v : G[u]) { // 遍历 u 的相邻节点
if (!dfn[v]) { // 如果未访问过
Tarjan(v); // 递归
low[u] = min(low[u], low[v]) // 未访问的和 low 取 min
if (low[v] == dfn[u]) // 标志着找到一个以 u 为根的点双连通分量
++cnt; // 增加方点个数
// 将点双中除了 u 的点退栈,并在圆方树中连边
for (int x = 0; x != v; --tp)
{
x = stk[tp];
T[cnt].push_back(x);
T[x].push_back(cnt);
}
// 注意 u 自身也要连边(但不退栈)
T[cnt].push_back(u);
T[u].push_back(cnt);
else
low[u] = min(low[u], dfn[v]); // 已访问的和 dfn 取 min
}
}
II.网络流
网络流是指一个特殊的有向图。
这个图的特殊点在于有两个特殊的点,源点和汇点,还有一个类似边权的容量
网络流满足以下限制
1.容量限制:对于每条边,流经该边的流量不得超过该边的容量
2.流守恒性:除源汇点外,任意结点 u的净流量为0.(也就是流入和流出的流量一样)
最大流
定义:
给定网络G及G上的流 f.
对于边(u, v),我们将其容量与流量之差称为剩余容量c(u,v)
我们将G中所有结点和剩余容量大于0的边构成的子图称为残量网络.
我们将G上一条从源点s到
Edmonds–Karp算法(简称EK)
对于每一个容量

浙公网安备 33010602011771号