【二分图匹配模板】

本文深入探讨了图论中的关键概念,包括无权图的最大匹配、最小点覆盖及它们之间的关系,以及有权图的最佳完美匹配算法。通过具体实例阐述了König定理、最小路径覆盖和匈牙利算法等核心思想。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

===【无权图最大匹配】===

 

1)一个二分图中的最大匹配数=这个图中的最小点覆盖数 

König定理是一个二分图中很重要的定理,它的意思是,一个二分图中的最大匹配数等于这个图中的最小点覆盖数。如果你还不知道什么是最小点覆盖,我也在这里说一下:假如选了一个点就相当于覆盖了以它为端点的所有边,你需要选择最少的点来覆盖所有的边。

 2)最小路径覆盖=|G|-最大匹配数

在一个N*N的有向图中,路径覆盖就是在图中找一些路经,使之覆盖了图中的所有顶点,且任何一个顶点有且只有一条路径与之关联;(如果把这些路径中的每条路径从它的起始点走到它的终点,那么恰好可以经过图中的每个顶点一次且仅一次);如果不考虑图中存在回路,那么每每条路径就是一个弱连通集.由上面可以得出: 

1.一个单独的顶点是一条路径; 
2.如果存在一路径p1,p2,......pk,其中p1 为起点,pk为终点,那么在覆盖图中,顶点p1,p2,......pk不再与其它的  顶点之间存在有向边. 最小路径覆盖就是找出最小的路径条数使之成为G的一个路径覆盖. 路径覆盖与二分图匹配的关系:最小路径覆盖=|G|-最大匹配数;

 

3)二分图最大独立集=顶点数-二分图最大匹配

独立集:图中任意两个顶点都不相连的顶点集合。

 

【匈牙利算法(邻接表)(n*m)】n表示顶点数,m表示边数

bool vis[MAXN];
int lin[MAXN], n;
vector<int> g[MAXN];
//n是左边点数的数量,g[i]:i表示左边的点标号,g[i]里面的是右边的点标号
bool dfs(int u)
{
	for(int i = 0; i < g[u].size(); i++)
	{
		int v = g[u][i];
		if(!vis[v])
		{
			vis[v] = true;
			if(-1 == lin[v] || dfs(lin[v]))
			{
				lin[v] = u;
				return true;
			}
		}
	}
	return false;
}
int hungary()
{
	int ans = 0;
	memset(lin,-1,sizeof(lin));
	for(int i = 0; i < n; i++)
	{
		memset(vis,0,sizeof(vis));
		if(dfs(i)) ans++;
	}
	return ans;
}


 【Hopcroft-Carp算法(sqrt(n)*m)】

#define rep(i,a,n) for(int i = a; i < n; i++)
#define repe(i,a,n) for(int i = a; i <= n; i++)
#define per(i,n,a) for(int i = n; i >= a; i--)
#define clc(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
typedef long long LL;
#define MAXN 510
#define MAXM 250100
struct Edge{
	int next, v;
	Edge(int a = 0, int b = 0){next = a, v = b;}
}edge[MAXM];
int head[MAXN], tol, n, dis, dx[MAXN], dy[MAXN], mx[MAXN], my[MAXN];
bool vis[MAXN];

void add_edge(int u, int v)
{
	edge[tol] = Edge(head[u], v);
	head[u] = tol++;
}
bool bfs()
{
	queue<int> q;
	dis = INF;
	clc(dx,-1);
	clc(dy,-1);
	rep(i,0,n)
	{
		if(-1 == mx[i])
		{
			q.push(i);
			dx[i] = 0;
		}
	}
	while(!q.empty())
	{
		int u = q.front();q.pop();
		if(dx[u] > dis) break;
		for(int i = head[u]; ~i; i = edge[i].next)
		{
			int v = edge[i].v;
			if(-1 == dy[v])
			{
				dy[v] = dx[u]+1;
				if(-1 == my[v]) dis = dy[v];
				else
				{
					dx[my[v]] = dy[v]+1;
					q.push(my[v]);
				}
			}
		}
	}
	return dis != INF;
}
bool dfs(int u)
{
	for(int i = head[u]; ~i; i = edge[i].next)
	{
		int v = edge[i].v;
		if(!vis[v] && dx[u]+1 == dy[v])
		{
			vis[v] = true;
			if(-1 != my[v] && dy[v] == dis) continue;
			if(-1 == my[v] || dfs(my[v]))
			{
				my[v] = u;
				mx[u] = v;
				return true;
			}
		}
	}
	return false;
}
int maxmatch()
{
	int ans = 0;
	clc(mx,-1);
	clc(my,-1);
	while(bfs())
	{
		clc(vis,0);
		rep(i,0,n)
		{
			if(-1 == mx[i] && dfs(i))
				ans++;
		}
	}
	return ans;
}


 


 


 


 ===【有权图最佳完美匹配】===

【KM算法 矩阵(n^3)】

#define rep(i,a,n) for(int i = a; i < n; i++)
#define repe(i,a,n) for(int i = a; i <= n; i++)
#define clc(a,b) memset(a,b,sizeof(a))
#define MAXN 110
#define INF 0x3f3f3f3f
struct KM{
	struct NODE{
		int to,w;
		NODE(int a, int b){
			to = a, w = b;
		}
	};
	int nx,ny;//两边的点数
	int linker[MAXN],lx[MAXN],ly[MAXN];//y中各点匹配状态,x,y中的点标号
	int slack[MAXN];
	bool visx[MAXN],visy[MAXN];
	int g[MAXN][MAXN];//二分图描述,纵坐标为左边,横坐标为右边,这里下标从1开始的
	bool dfs(int x)
	{
		visx[x] = true;
		repe(y,1,ny)
		{
			if(visy[y]) continue;
			int tmp = lx[x]+ly[y]-g[x][y];
			if(!tmp)
			{
				visy[y] = true;
				if(-1 == linker[y] || dfs(linker[y]))
				{
					linker[y] = x;
					return true;
				}
			}
			else if(slack[y] > tmp)
				slack[y] = tmp;
		}
		return false;
	}
	int max_km()
	{
		clc(linker,-1);
		clc(ly,0);
		repe(i,1,nx)
		{
			lx[i] = -INF;
			for(int j = 0; j < ny; j++)
				if(g[i][j] > lx[i]) lx[i] = g[i][j];
		}
		repe(x,1,nx)
		{
			clc(slack,0x3f);
			while(1)
			{
				clc(visx,0);
				clc(visy,0);
				if(dfs(x))break;
				int d = INF;
				repe(i,1,ny)
				{
					if(!visy[i] && d > slack[i])
						d = slack[i];
				}
				repe(i,1,nx) if(visx[i]) lx[i] -= d;
				repe(i,1,ny)
				{
					if(visy[i]) ly[i] += d;
					else slack[i] -= d;
				}
			}
		}
		//最后统计最大值,加符号就是最小值
		int ans = 0;
		repe(i,1,ny)
		{
			if(INF == g[linker[i]][i]) return -1;//不存在完美匹配,返回-1
			if(~linker[i]) ans += g[linker[i]][i];
		}
		return -ans;
	}
}km;


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值