二分图 & SCC 学习笔记 (7.3~7.8)
二分图 & SCC 学习笔记 (7.3~7.8)
1. 基础概念
二分图:指一无向图,使得存在两个集合,使得两个集合内任意二点不存在连边。
注:树一定是二分图。
匹配:由一组没有公共端点的不是环的边构成的集合。其中任意两条边没有共同节点。
最大匹配:在图中使得总边数最大的匹配。
增广路:若在一条路径中,某匹配集合中的边与不在匹配集合中的边交替出现,则称该路径为增广路径。
注:两端连边均为未匹配边。
强连通:在有向图$V$中任意两个节点存在路径连通。
强连通分量:在图$G$中的极大(指加入图G中其他任意一条边都会使其不再强联通)强联通子图。
2.算法思路
匈牙利(增广路)算法:
解决二分图最大匹配问题。
核心为不断地寻找增广路来扩充匹配集合。
因为增广路中未匹配边 = 匹配边 + 1
所以可将增广路中未匹配边匹配,最大匹配 + 1.
伪代码:
while found u[i]->v[j]:
if !(v[i] ∈ M):#M为增广路径集合
push(v[i]) -> M
if v[i]未覆盖 || v[i]的原覆盖点能找到增广路:
v[i]覆盖点 = u[i]
return True
return False
$Code$:
int n, m, q;
int road[maxm], mk[maxm];
vector <int> g[maxn];
bool find(int x){
for(int i = 0; i < g[x].size(); i++){
if(mk[i] < dfn){
if(!road[i] || find(road[i])){
road[i] = x;
return 1;
}
}
}
return 0;
}
int main(){
cin >> n >> m >> q;
for(int i = 1; i <= q; i++){
int x, y;
cin >> x >> y;
g[x].push_back(y);
}
for(int i = 1; i <= m; i++)
dfn++, ans += find(i);
return 0;
}
2. Tarjan算法
找到图中的所有强联通分量。
前置芝士:DFS生成树
Tarjan算法中为每个节点 $u$ 维护了以下数组:
$dfn_u$:深度优先搜索遍历$DFS$搜索树时节点 $u$ 被遍历的次序,即点 $u$ 在树中的编号。
$low_u$:在 $u$ 的子树中通过返祖边能狗够回溯到的最早且在栈中的节点。
令 $Subtree_u$ 为以 $u$ 为根的子树。
$low_u$ 定义为 $Subtree_u$ 的节点或由 $Subtree_u$ 的返祖边能到达的节点的 $dfn$ 的最小值。
按照节点的 $dfs$ 序对图中所有的节点进行搜索,并维护节点的 $low,dfn$ 值。
让当前被搜索到的节点入栈。
每找到一个强联通元素,便让栈中所有属于该元素的节点出栈。
在搜索过程中,对于二节点 $(u,v)$,满足 $v.fath≠u$ 考虑:
- $v$ 未被搜索到:$dfs(v)$,在回溯过程中,用 $low_v$ 更新 $low_u$。
- $v$ 被搜索到,但不在栈中:$v$ 已搜索完毕,其 $ SCC$ 已被处理,不用操作。
- $v$ 被搜索到,在栈中:用$dfn_v$ 更新 $low_u$。
搜索结束后,若有 $dfn_u=low_u$ ,则依次弹出栈内元素,并做处理。(栈中 $u$ 及其上方的节点构成一个 $SCC$)
伪代码:
func tarjan(int u):
mk[u] = True, low[u] = dfn[u] = ++dfnCnt
push(u) -> stack
for found (u, v):
if !mk[u]:
tarjan(v)
low[u] = min(low[u], low[v])
elif inStack[v] == True:
low[u] = min(low[u], dfn[v])
if dfn[u] == low[u]:
sc++
while True:
scc[stack.top] = sc
size[sc]++
instack[stack.top] = False
stack.pop
if(stack.top == u)break
$Code:$
int n;
int dfn[maxm], low[maxm], inStack[maxm], scc[maxm], Size[maxm], res[maxm], s[maxm];
int mk[maxm];
int dfncnt, sc, v, ans, top;
vector <int> g[maxm];
void tarjan(int u){
low[u] = dfn[u] = dfncnt;
s[++top] = u;
inStack[u] = 1;
for(int i = 0; i < g[u].size(); i++){
int v = g[u][i];
if(!dfn[v])tarjan(v), low[u] = min(low[u], low[v]);
else if(inStack[v])low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u]){
sc++;
while(s[top] != u)scc[s[top]] = sc, Size[sc]++, inStack[s[top]]=0, top--;
scc[s[top]] = sc, Size[sc]++, inStack[s[top]]=0, top--;
}
}
int main(){
input();
for(int i = 1;i <= n;i++)
if(!dfn[i])tarjan(i);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】