(笔记)二分图最大匹配 匈牙利算法

二分图最大匹配 匈牙利算法

概念

二分图:将图划分为两个点集,使得同一点集中的点互相没有直接相连的边

判断方法(互相独立):

  1. 二分图中任意环的边数都是偶数

  2. 利用并查集辅助染色

  3. 跑搜索

匹配:在二分图中,找到一组边集,使得其中任意两条边都没有公共顶点

(令匹配边表示为 \(A\),未匹配边表示为 \(B\)

交替路:一条路径形如 \(A - B - A - B ...- A - B\) 即为交替路,其中 \(A\)\(B\) 数量相等

增广路:在交替路的末尾增加一条未匹配边

最大匹配:令匹配边数量最大化

最小点覆盖:选取最少的点,使二分图中每条边都有至少一个端点被选择

最大独立集:选取最多的点,使二分图中该点集中任意两点互不相连

最小路径覆盖数:对于一个有向无环图,选取最少条路径,使得每个顶点属于且仅属于一条路径。路径长可以为 \(0\)(即单个点)。

定理1:\(\text{最大匹配数 = 最小点覆盖数}\)

定理2:\(\text{最大匹配数 = 最大独立数}\)

定理3:\(\text{最小路径覆盖数 = 顶点数-最大匹配数}\)

(摘自二分图最大匹配、完美匹配和匈牙利算法

算法主体

利用贪心思想,对二分图左侧点逐个作为起点寻找增广路,然后将增广路中的匹配边与不匹配边互换,即可得到比原来多一边的匹配边。重复上述操作,即可得到最大匹配。算法实现的时间复杂度为 \(O(nm)\),其中 \(n\) 指左部图的数量,\(m\) 为图中所有边的数量。

Hall 定理

对于一个二分图 \(G(X,Y)\)\(X\) 存在一个匹配的充分必要条件为对于 \(X\) 的任意子集 \(S\)\(S\) 的邻居个数 \(N(S)\) 必须大于等于 \(S\) 的大小 \(|S|\)

例题

P3386 【模板】二分图最大匹配

P1129 [ZJOI2007] 矩阵游戏

P2055 [ZJOI2009] 假期的宿舍

P7368 [USACO05NOV] Asteroids G

P3386 【模板】二分图最大匹配

模板代码贴贴:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1005;
int n,m,e,ans;
vector<int>G[N];
int mat[N],vst[N];
bool dfs(int x,int tag){
	if(vst[x]==tag)return false;
	vst[x]=tag;
	for(auto v:G[x]){
		if((mat[v]==0)||(dfs(mat[v],tag))){
			mat[v]=x;
			return true;
		}
	}
	return false;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m>>e;
	for(int i=1;i<=e;i++){
		int u,v;
		cin>>u>>v;
		G[u].push_back(v);
	}
	for(int i=1;i<=n;i++){
		if(dfs(i,i)){
			ans++;
		}
	}
	cout<<ans;
	return 0;
}

P1640 [SCOI2010] 连续攻击游戏

建模,将攻击建为左部图的节点,武器建为右部图的节点。对两边进行连边,可以发现每个武器一定对左部图两个点有直接连边,然后直接按顺序跑最大匹配即可,要注意如果匹配失败就直接退出,因为是连续攻击段。由于攻击 \(\in[1,10^4]\),所以左部点数量 \(\in[1,10^4]\),总边数理论上是 \(10^6\) 级别的,但是根据一些人类智慧根本不可能在每次 DFS 都跑满整张图,所以常数小就是了。

#include<bits/stdc++.h>
using namespace std;
const int N=2e6+5;
int n,mat[N],vis[N];
vector<int>G[N];
bool dfs(int u,int tag){
	if(vis[u]==tag)return 0;
	vis[u]=tag;
	for(int v:G[u]){
		if((!mat[v])||dfs(mat[v],tag)){
			mat[v]=u;
			return 1;
		}
	}
	return 0;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		G[v+n].push_back(i);
		G[u+n].push_back(i);
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		if(dfs(i+n,i+n))
			ans++;
		else break;
	}
	printf("%d",ans);
	return 0;
}

AT_abc320_g [ABC320G] Slot Strategy 2 (Hard)

考虑选的数字 \(num\),直接枚举即可。考虑时间限制,显然可以直接二分答案计算,然后我们就需要解决一个判定性问题,这 \(n\) 个字符串是否可以在 \(\le lim\) 的时间内被选完。考虑转化为二分图问题,那么左部点就是所有字符串,右部点就是所有时间。对每个字符串,对其前 \(n\) 个合法且不超过 \(lim\) 的时间进行连边,然后对整张图跑二分图最大匹配,看看能否匹配 \(n\) 对。这里我们使用匈牙利算法。边数是 \(O(n^2)\) 级别的,那么 \(\text{check}\) 一次的时间复杂度就是 \(O(n^3)\) 的,加上二分还有一定常数就是 \(O(n^3\log nm)\) 的。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m,res=1e9;
char c[105][N];
vector<int>P[105][12];
int mat[N*50],vis[105];
int num;
vector<int>G[105],Del;
bool dfs(int u,int tag){
	if(vis[u]==tag)return 0;
	vis[u]=tag;
	for(int v:G[u]){
		if((!mat[v])||dfs(mat[v],tag)){
			mat[v]=u;
			return 1;
		}
	}
	return 0;
}
bool check(int &lim){
	int ans=0;
	for(int i=1;i<=n;i++)
		G[i].clear(),vis[i]=0;
	for(int i=1;i<=n;i++){
		if(!P[i][num].size())return 0;
		int cnt=0,j=P[i][num][0],pos=0,cgt=0;
		while(cnt+1<=n&&j<=lim){
			cnt++;
			G[i].push_back(j);
			Del.push_back(j);
			pos++;
			if(pos>=P[i][num].size())
				cgt++,pos=0;
			j=P[i][num][pos]+cgt*m;
		}
	}
	for(int i:Del)mat[i]=0;
	Del.clear();
	for(int i=1;i<=n;i++){
		if(dfs(i,i))ans++;
		else return 0;
	}
	return 1;
}
int ef(){
	int l=0,r=n*m,res=n*m+1;
	while(l<=r){
		int mid=(l+r)>>1;
		if(check(mid))res=mid,r=mid-1;
		else l=mid+1;
	}
	return res;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%s",c[i]);
		for(int j=0;j<m;j++)
			P[i][c[i][j]-'0'].push_back(j);
	}
	for(num=0;num<10;num++)
		res=min(res,ef());
	if(res==n*m+1)printf("-1");
	else printf("%d",res);
	return 0;
}
posted @ 2025-04-24 14:52  TBSF_0207  阅读(59)  评论(0)    收藏  举报