二分图(染色法和匈牙利算法)

二分图的定义:

二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。简而言之,就是顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻。

二分图的判断:

对于二分图的问题我们首先要判断一个图它是不是二分图。对于二分图的判断方法最常见的是染色法,顾名思义就是我们对每一个点进行染色操作,我们只用黑白两种颜色,问能不能使所有的点都染上了色,而且相邻两个点的颜色不同,如果可以那么这个图就是一个二分图,对于判断是否是一个二分图的方法可以用dfs和bfs两种方式去实现。

很经典的性质: 一个图是二分图 当且仅当图中不含奇数环 反之也成立

例题:

染色法判定二分图:

思路:

看看这个点染色没有,要是没有我就给它染上1的颜色 然后 递归和他相连的顶点的颜色都不能和它一样 要是冲突的话 return false;

dfs里面两个参数分别是 当前这个点 这个点染的颜色(1或者是2)

代码:

#include<bits/stdc++.h>

using namespace std;

const int N = 100010, M = 200010;

int h[N], e[M], ne[M], idx;
int n, m;
int color[N];//这里染色的取值是 1 或 2 

void add(int x, int y) {
	e[idx] = y;
	ne[idx] = h[x];
	h[x] = idx++; 
}

bool dfs(int len, int c) {
	color[len] = c;
	for(int i = h[len]; i != -1; i = ne[i]) {
		int j = e[i];
		if(color[j] == 0) {//当前这个点没被染过颜色 我就让他染一下颜色 而且染得颜色要和len的颜色要不一样 
		  if(dfs(j, 3 - c) == false)return false; //递归看看我下面的有没有错误的  
		} else if(color[j] == c){//一条边的顶点颜色一样 就不是二分图了 
			return false;
		}
	}
	return true;
} 

signed main() {
	cin >> n >> m;
	memset(h, -1, sizeof h);
	
	for(int i = 1; i <= m; ++ i ) {
		int x, y;
		cin >> x >> y;
		add(x, y);
		add(y, x); //无向图
	} 
	
	bool flag = true;
	//看看每个点能不能被染色 
	for(int i = 1; i <= n; ++ i ) {
		if(color[i] == 0) {//还没被染色 
			if(dfs(i, 1) == false) {//染色失败了  
				flag = false;
				break;
			}
		}
	}
	if(flag)cout << "Yes" << endl;
	else cout << "No" << endl;
	return 0;
}

匈牙利算法

思路:

证明不会证明 那就记住吧

下面是个简单的例子和说明

人物: 小明 小红 小刚 小美

人物之间的关系: 小明和小刚都喜欢小红 ,小刚(花心)也喜欢小美

枚举到小明的时候, 小明喜欢的妹子是小红 要是没人喜欢小红的话,那小红就是小明的了 但是小刚也喜欢小红,小明就去和小刚说一下:"哥们,能不能把小红让给我啊,求求你了!!!", 小刚说:"哥们不是我不愿意让你, 要是小美是我的了,我就把小红让给兄弟你" 小刚又看向那些喜欢小美的人, 就这样递归下去

代码:

#include<bits/stdc++.h>

using namespace std;

const int N = 510, M = 1e5 + 10;

//匈牙利算法 尝试 不撞南墙不回头 最坏的时间复杂度是O(nm) 
//二分图的最大匹配
int h[N], ne[M], e[M], idx;
int match[N];//右边的人匹配对应到左边人的位置 
int n1, n2, m, ret;
bool st[N];

void add(int x, int y) {
	e[idx] = y;
	ne[idx] = h[x];
	h[x] = idx++;
}

bool find(int x) {
	for(int i = h[x]; i != -1; i = ne[i]) {
		int j = e[i];
		if(st[j] == false) {//这个st数组还是很关键的 别是这一轮中这个j点还没被尝试过 那我就试一下嘛 
			st[j] = true;
			//下面这个是经典啊 
			//和我相连的这个点 没有和其他点连在一起 或者是 虽然连到一起了但是连的那个人他可以换一个人 
	     	if(match[j] == 0 || find(match[j]) == true) {
	     		match[j] = x;//这个记录的是下标 意思是和j匹配的点的位置是x 
		   	    return true;//匹配到了 
		    }
		}
	
	} 
	return false;//找不到匹配的 遗憾退场 
}

signed main() {
	//左边n1个点 右边n2个点 m条变 数据保证是二分图
	memset(h, -1, sizeof h);
	cin >> n1 >> n2 >> m;
	for(int i = 1; i <= m; ++ i ) {
		int x, y;
		cin >> x >> y;
		add(x, y);
	} 
	for(int i = 1; i <= n1; ++ i ) {
		//因为每次模拟匹配的预定情况都是不一样的所以每轮模拟都要初始化
		memset(st, false, sizeof st);
		if(find(i))ret++;
	}
	cout << ret << endl;
	return 0;
}

  • 17
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FindYou.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值