复习心得 - 二分图

以前写过一篇匈牙利(增广路)算法,现在系统复习一下二分图。

定义

就是两排点,同一排之间两两不连边的图。

判断

可能不完全连通,从每一个新的点开始黑白染色判断即可。

#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\)。

  1. 如果 \(E'\) 中任意两条边没有相同节点,则 \(E'\) 为 \(G\) 的匹配,设其为 \(M\)。
  2. 我们取 \(\max |E'|\) 的 \(E'\) 为最大匹配,其中 \(|E'|\) 为 \(G\) 的匹配数。
  3. 要是 \(|E'|=|V_1|\),即 \(V_1\) 中所有点都找到匹配了,那么这个匹配为完备匹配。

当然我们还有概念(我之前看不懂的):

  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\) 是交错路径。
  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\) 个点一个入一个出跑二分图


https://www.luogu.com.cn/article/h0vo4isv

posted @ 2026-10-12 11:36  Ivan422  阅读(3)  评论(0)    收藏  举报