学习NOTE 8——二分图

好久没写图论的学习笔记了,来写一发最近做题学的二分图

是什么

二分图就是类似于把一个图分成两半,分为左部点和右部点。听上去是“二分”,其实和二分算法没什么关系,就是一个分成两半的图。

举个例子:

就是像上图一样把一个图分成左部点和右部点两个部分。

那这是用来干什么的?
一般是在一个图上求出最大匹配数量/判断能否把点分成两个集合,且所有边都在集合之间。上面两种主要的问题一般用匈牙利算法染色法解决。

怎么实现

上述两种算法我们分开讲。

1.匈牙利算法

要看最大匹配数量,那我们可以从一方入手,因为通常题目中都会有明确的“主动方”与“被动方”(?)。
这里我们用左边去比配右边,先从图入手:
graph (1)
1先和5匹配。现在还剩2,3,4和6,7,8。
graph (1)
2要和8匹配,但是3又不乐意了,就去和6匹配了。
graph (1)

结果4就没得匹配了,但是2还可以换啊。
graph (1)
2无奈,转头去找7。这时6,8都空出来了,4选6、3选8,完美!
graph (1)

代码实现:
首先建图,这个时候就要建单向边了 (双向奔赴可不好整啊)
接下来就用dfs跑一遍就行了,但是为什么要用dfs?(因为我是大啥子) 啊不,是因为用DFS递归模拟"占位者层层让位"的过程,直到找到一个空闲的右点或所有路径都试完,实现增广路的查找。而且,用dfs比用bfs要好写的多

(注:增广路:一条从"未匹配左点"出发,交替经过"未匹配边→匹配边→未匹配边→...",最终到达"未匹配右点"的路径。)

伪代码如下:

dfs(u, sfn): // 尝试为左点u找匹配,sfn标记本轮轮次
    if dfn[u] == sfn: return false // 本轮已试过u,防死循环
    dfn[u] = sfn // 标记u本轮已访问
    for v in g[u]:// 遍历u能匹配的所有右点
        if !rec[v] or dfs(rec[v], sfn):// v空闲 或 让v的匹配者另找
            rec[v] = u// v匹配给u
            return 1
    return 0 // 都试过了,找不到
------------------------------------------------
main():
    读入 n, m, e
    建图 g[u].push_back(v)
    ans = 0
    for i = 1 to n:// 每个左点尝试一次
        if dfs(i, i)://i作为轮次编号
            ans++
    输出 ans

这样,代码就很好写了!
放一下题:P3386
进行dfs,对每一个左点尝试匹配所有可连的右点,如果右点空闲则直接匹配,如果右点已被其他左点匹配,则递归尝试让那个左点另找右点,直到所有左点都尝试完毕,统计成功匹配的数量。

$\color{Cyan}Code$
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e4+10;
int n,m,e,ans,dfn[N],rec[N];//dfn:dfs序,rec:匹配的东西
vector<int> g[N];
bool dfs(int u,int sfn){
	if(dfn[u]==sfn) return 0;
	dfn[u]=sfn;
	for(auto v:g[u])
		if(!rec[v]||dfs(rec[v],sfn)) return (rec[v]=u,1);
	return 0;
} 
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m>>e;
	for(int i=1,u,v;i<=e;i++){
		cin>>u>>v;
		g[u].push_back(v);
	}
	for(int i=1;i<=n;i++) if(dfs(i,i)) ans++;
	cout<<ans;
	return 0;
}

2.染色

还是这个图:
graph
只是换一下:
graph
改成一条链的形式(刚刚的太乱了)。
现在我们把每个点染色,分成两个阵营。但是二分图染色有个要求,就是相邻两个点颜色不能一样。
就成了下面这样:
graph
欸,怎么刚好1,2,3,4一个阵营,5,6,7,8一个阵营,你就说是不是分成两半了吧。

代码实现
这个算法的代码就更简单了!

但是上面的图不够好,画一个更好的:
graph
染色:
无标题
1,3,4一个阵营,2单独一个阵营。

ok,上题:P10378
考虑将两个学校两个阵营,然后染色,每次找最大、小值并求和就行了。
伪代码:

dfs(u, c):// u:当前点, c:颜色(1或0)
    flg[u] = 1// 标记已访问
    if(c) s1++// 颜色1计数
    else s2++// 颜色0计数
    for v in g[u]:// 遍历所有邻居
        if (!flg[v])// 未访问
            dfs(v, !c)// 染相反颜色
main():
    读入n, m
    建图: g[u].push_back(v), g[v].push_back(u)  // 双向边
    for i = 1 to n:
        if (!flg[i]): // 新连通块
            s1 = s2 = 0
            dfs(i, 1)// 从颜色1开始染
            mn += min(s1, s2// B最少: 选人数少的
            mx += max(s1, s2)// B最多: 选人数多的
    输出 mn mx
$\color{Orange}Code$
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10;
int n,m,flg[N],mn,mx,s1,s2;
vector<int> g[N];
void dfs(int u,bool c){
	flg[u]=1;
	if(c) s1++;
	else s2++;
	for(auto v:g[u])
		if(!flg[v]) dfs(v,!c);
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	for(int i=1;i<=n;i++)
		if(!flg[i]){
			s1=s2=0;
			dfs(i,1);
			mn+=min(s1,s2);
			mx+=max(s1,s2);
		}
	cout<<mn<<" "<<mx;
	return 0;
}

TIP

注意:一定要分清楚题目要的是哪种方法,一定要读题,别问我怎么知道的

posted @ 2026-09-08 17:02  _T_M_T  阅读(0)  评论(0)    收藏  举报