二分图的匹配

定义

有点扩展域并查集的意思~

如果一张无向图的 \(N\) 个节点 \((n\geq 2)\) 可以分成 \(A,B\) 两个非空集合,其中 \(A\cap B = \emptyset\),并且在同一集合内的点之间都没有边相连,那么称这张无向图为一张二分图\(A\)\(B\) 分别称为二分图的左部和右部。

img

性质

  • 如果两个集合中的点分别染成黑色和白色,可以发现二分图中的每一条边都一定是连接一个黑色点和一个白色点。
  • 二分图不存在长度为奇数的环。
    • 因为每一条边都是从一个集合走到另一个集合,只有走偶数次才可能回到同一个集合。

二分图判定

定理

由性质可推出判定定理:

一张无向图是二分图,当且仅当图中不存在奇环(长度为奇数的环)。

方法

根据该定理,我们可以用染色法进行二分图判定。

大致思想为:

  1. 尝试用黑白两种颜色标记图中的节点。
  2. 当一个节点被标记后,它的所有相邻节点应该被标记为与它相反的颜色。
  3. 若标记过程中产生冲突,说明图中存在奇环。

基于 \(DFS\) 的伪代码,时间复杂度为 \(\text O(N + M)\)

void dfs(int x, int color)
{
	赋值 v[x] <- color
    对于与 x 相连的每条无向边 (x,y)
        if v[y] = 0 then
            dfs(y, 3 - color);//因为只有1、2两种颜色,3-1 = 2, 3-2 = 1
    	else if V[y] == color
            判定该图 不是二分图,算法结束
}

在主函数中
	for i <- 1 to N
		if v[i] = 0 then dfs(i, 1)
    判定无向图 是二分图

例题

对最大冲突值二分答案。

把所有仇恨程度大于当前 \(mid\) 的罪犯分到两个集合(在这两个罪犯之间建边)。

这张无向图的节点需要被分成两个集合(两个监狱),并且集合内部没有边(同一个监狱内没有仇恨程度大于 \(mid\) 的罪犯)。所以,我们用染色法判定是否为二分图即可。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<vector> 
#include<cstring>

using ll = long long;

const int N = 2e4 + 10;
const int M = 1e5 + 10;

std::vector<int> v[N];

int vis[N];
int n, m;
ll l, r, mid;

struct Node{
	int x, y; ll value;
	Node(int _x, int _y, ll _v): 
		x(_x), y(_y), value(_v) {}
	Node() {}
	void read() {std::cin >> x >> y >> value; r = std::max(r, value);}
	void setMap(int t) {if (value > t) v[x].push_back(y), v[y].push_back(x);}
}query[M];

void init()
{
	std::cin >> n >> m;
	int x, y, t;
	for (int i = 1; i <= m; i++) query[i].read(); 
}

bool dfs(int x, int color)
{
	vis[x] = color;
	for (auto to : v[x])
	{
		if (vis[to] == 0) 
		{
			if (!dfs(to, 3 - color)) return false;
		}
		else if (vis[to] == color)
			return false;
	}
	return true;
}

bool check(int x)
{
	memset(vis, 0, sizeof(vis));
	for (int i = 1; i <= n; i++) v[i].clear();
	for (int i = 1; i <= m; i++)
	{
		query[i].setMap(x);
	}
	for (int i = 1; i <= n; i++)
	{
		if (vis[i] == 0)
		{
			if (!dfs(i, 1)) return false;
		}
	}
	return true;
}

int main()
{
 	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	std::cout.tie(nullptr);
	init(); 
	while(l <= r)
	{
		mid = l + ((r - l) >> 1);
		if (check(mid)) r = mid - 1;
		else l = mid + 1;
	}
	std::cout << r + 1;
	return 0;
}

二分图最大匹配

posted @ 2023-12-08 15:00  加固文明幻景  阅读(8)  评论(0编辑  收藏  举报