复习心得 - 二分图
以前写过一篇匈牙利(增广路)算法,现在系统复习一下二分图。
定义
就是两排点,同一排之间两两不连边的图。
判断
可能不完全连通,从每一个新的点开始黑白染色判断即可。
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m,col[N];
vector<int>e[N];
bool is;
bool dfs(int p,int pre){
if(!pre)
col[p]=1;
else col[p]=3-col[pre];
bool res=1;
for(auto v:e[p]){
if(!col[v])
res&=dfs(v,p);
else if(col[v]==col[p])
return 0;
}
return res;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
is=1;
for(int i=1;i<=n;i++)if(!col[i])
is&=dfs(i,0);
cout<<(is?"Yes":"No");
return 0;
}
最大匹配
详见我写的匈牙利算法。
这里介绍一些别的方法。
这里补充一点概念:
二分图 \(G\) 的顶点集分为两部分 \(V_1\) 与 \(V_2\),其中满足 \(|V_1|\leq |V_2|\),并选择边 \(E'\in E\)。
- 如果 \(E'\) 中任意两条边没有相同节点,则 \(E'\) 为 \(G\) 的匹配,设其为 \(M\)。
- 我们取 \(\max |E'|\) 的 \(E'\) 为最大匹配,其中 \(|E'|\) 为 \(G\) 的匹配数。
- 要是 \(|E'|=|V_1|\),即 \(V_1\) 中所有点都找到匹配了,那么这个匹配为完备匹配。
当然我们还有概念(我之前看不懂的):
-
交错路径:在 \(G\) 上的一条路径 \(P\) 其中 \(P_i\to P_{i+1} (1<i< |P|)\) 边与 \(P_{i-1}\to P_i\) 边其中一条 \(\in M\),一条 \(\notin M\)。
- 例子:当前有匹配 \(\{1_1\to 1_2,2_1\to 3_2\}\),那么当前路径 \(P=1_1\to 1_2\to 2_1\to3_2\) 是交错路径。
-
【重要】增广路径:首先 \(P\) 是交错路径。然后 \(P_1\) 与 \(P_{|P|}\)(起点与终点)都未被匹配。 这个东西的作用是 \(M\oplus P\)(集合异或)更优。
- 例子:\(P=3_1\to3_2{\color{red}\to}2_1\to1_2{\color{red}\to}1_1\to 2_2\),你会发现这其实是上面例子加上了两个新的点两条边,其中原有匹配就是 \(\{1_1\to 1_2,2_1\to 3_2\}\)。
- 证明 \(M\oplus P\) 更优:
- 在增广路径 \(P\) 中,是匹配的边比不是匹配的边多 \(1\) 条(匹配边间隔一个非匹配边)被影响的边是在路径上的匹配边(\({\color{red}\to}\))它们不再是匹配边,而其他的边(\(\to\))成为了匹配边。
- 原先的匹配边为 \(n\) 条,则非匹配边为 \(n-1\) 条,增加新的两条边让非匹配边变成 \(n-1+2=n+1\) 条,比原先的匹配边多了 \(1\) 条。
- 综上,增广路径 \(P\) 可以替换原先 \(M\),变为 \(M\oplus P\),会有 \(1\) 贡献。
我们发现了增广路径的性质之后,自然想到如何匹配。
我们发现匈牙利本质就是找增广路。虽然这很慢,lol。
考虑枚举一个点作为增广路的起点(\(p\in V_2\)),然后你神奇的发现我们的匈牙利(增广路算法)本质就是不停扩展增广路。
就结束了。
代码
水文尼蜡。
#include<bits/stdc++.h>
using namespace std;
const int N=5e3+10;
int n,m,ee,U,V,npy[N],ncnt;
int found[N];
bool e[N][N];
bool dfs(int p){
for(int v=1;v<=m;v++)if(e[p][v]&&!npy[v]){
npy[v]=1;
if(!found[v]||dfs(found[v])){
found[v]=p;
return 1;
}
}
return 0;
}
int main(){
cin>>n>>m>>ee;
for(int i=1;i<=ee;i++){
cin>>U>>V;
e[U][V]=1;
}
for(int i=1;i<=n;i++){
memset(npy,0,sizeof(npy));
if(dfs(i))++ncnt;
}
cout<<ncnt;
return 0;
}
最后,祝愿广大 OIer 早日找到 NPY
(虽然这不可能——From Anguei
trick: 把有向图 \(n\) 拆 \(2n\) 个点一个入一个出跑二分图
文章同步载入于洛谷,详见文章下方链接。
本文来自博客园,作者:Ivan422,转载请注明原文链接:https://www.cnblogs.com/Exfish/p/23269810。

浙公网安备 33010602011771号