算法随笔——二分图

定义

节点由两个集合组成,且两个集合内部没有边的图

性质

  • 无奇环
  • 每条边都是从一个集合走向另一个集合。

二分图判定

使用染色法

进行 \(dfs\),为图进行黑白染色,若可以完成则该图是二分图。

bool vis[N];//0:未染色,1:黑色,2:白色
bool flag= 1;
void dfs(int x)
{
	for (auto y : v[x])
	{
		if (!col[y]) col[y] = 3-col[x],dfs(y);
		else if (col[y] != 3-col[x]) flag = 0;
	}
}

二分图最大匹配

即找到最多条边使两个集合的点匹配。

使用匈牙利算法

算法思想是在已有的匹配基础上考虑能否一种新的匹配方式使得匹配数 + 1,俗称增广路。

bool dfs(int x)
{
	for (auto y : v[x])
	{
		if (vis[y]) continue;
		if (!p[y] || dfs(p[y])) 
		{
			p[y] = x;
			return 1;
		}
	}
	return 0;
}

例题

P3386

P3386

没什么好说的,板子题。

int n,m,e;

vector<int> v[N];

int p[N];
bool vis[N];
void add(int x,int y){v[x].push_back(y);}
bool dfs(int x)
{
	for (auto y :v[x])
	{
		if (vis[y]) continue;
		vis[y] = 1;
		if (p[y]==0 || dfs(p[y]))
		{
			p[y] = x;
			return 1;
		}
	}
	return 0;
}

int main()
{
	n = read(),m = read(),e = read();
	for (int i = 1;i <= e;i++)
	{
		int u = read(),v = read();
		v += n;
		add(u,v);
		add(v,u);
	}
	int ans = 0;
	for (int i = 1;i <= n;i++)
	{
		memset(vis,0,sizeof vis);
		ans += dfs(i);
	}
	cout << ans << endl;
	return 0;
}

P2071

P2071

挺有意思的二分图匹配变种题。

与二分图正常匹配唯一区别是可以一个点匹配两个点。

做法很简单,就是将一边的点复制一份,这样就变成了一个点匹配一个点了。

CF1139E

CF1139E

感觉是个非常好的题。

非常适合作为二分图的练习题。

我们先考虑将操作倒序,此时变成每次加入一个学生。

然后注意到”校长将会从每个社团中选出一个人“,若我们将能力值与拥有该能力值的学生所属社团连边,似乎就变成了一个二分图匹配的问题。

但是 \(mex\) 还是不好求。

想法 \(1\) 是每次加入后二分 \(mex\) 的值,然后判断能否做到完全匹配。

时间复杂度 \(O(n^3 \log n)\),显然过不去。

考虑优化,我们发现每次二分判断是否是完全匹配时都会遍历 \([0,mid]\)\(dfs\)

那么我们其实可以从 \(0\) 开始往后枚举,直到无法找到增广路便 \(break\) ,这样就省去了一个二分。

时间复杂度 \(O(n^3)\),还是过不去。

注意到我们每次加入一条边时对于原来已有的匹配是没有影响的,即答案满足单调性。

那么我们每次加边的时候就不需要从 \(0\) 开始往后遍历,直接从上一次的 \(mex\) 开始即可。

时间复杂度分析:整个算法跑下来相当于只跑了一遍匈牙利,复杂度 \(O(n^2)\)。本题搞定!

posted @ 2024-09-20 15:30  codwarm  阅读(49)  评论(0)    收藏  举报