二分图

知识梳理

概念

如果一张无向图的 N N N 个节点,可以分成 A 、 B A、B AB 两个非空集合,其中A ∩ \cap B = = = ∅ \varnothing ,并且同一集合内的点都没有边相连,那么称这张无向图为二分图

二分图判定

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

用染色法判定此图是否为二分图
尝试用黑白两色标记图中节点,当一个节点被标记,它的所有相邻节点应该被标记为另一种颜色
若标记过程产生冲突,则说明存在奇环
void dfs(int x, int color) {
	v[x] = color;
	遍历所有与x相连的无向边
	if(!v[y]) dfs(y, 3-color);
	else if(v[y] = color) 判定该无向图不是二分图,算法结束
}
二分图最大匹配

任意两条边没有公共端点 的边的集合被称为图的一组匹配
在二分图中,包含边数最多的一组匹配被称为 二分图的最大匹配
任意一组匹配,属于 S S S 的边被称为匹配边,另外的称为非匹配边, 匹配边的端点被称为匹配点,其他节点被称为非匹配点
在这里插入图片描述

例题

Place the Robots

题目描述

Robert is a famous engineer.
One day he was given a task by his boss.
The background of the task was the following:
Given a map consisting of square blocks. There were three kinds of blocks: Wall, Grass, and Empty.
His boss wanted to place as many robots as possible in the map.
Each robot held a laser weapon which could shoot to four directions (north, east, south, west) simultaneously.
A robot had to stay at the block where it was initially placed all the time and to keep firing all the time.
The laser beams certainly could pass the grid of Grass, but could not pass the grid of Wall.
A robot could only be placed in an Empty block.
Surely the boss would not want to see one robot hurting another.
In other words, two robots must not be placed in one line (horizontally or vertically) unless there is a Wall between them.
Now that you are such a smart programmer and one of Robert’s best friends, He is asking you to help him solving this problem.
That is, given the description of a map, compute the maximum number of robots that can be placed in the map.

翻译

给定一 个 m × n m×n m×n大小的地图方格,有3 种方格-墙、草地和空地,机器人可以朝四个方向开枪。激光不能穿透墙壁,且只能放置在空地,两个机器人不能放在同一行(水平或垂直),除非他们之间有一堵墙格开。 给定一张地图,你的程序需要输出在该地图中可以放置的机器人的最大数目。

Input
The first line contains an integer T (<= 11) which is the number of test cases. For each test case, the first line contains two integers m and n (1<= m, n <=50) which are the row and column sizes of the map. Then m lines follow, each contains n characters of ‘#’, ‘*’, or ‘o’ which represent Wall, Grass, and Empty, respectively.

Output
For each test case, first output the case number in one line, in the format: “Case :id” where id is the test case number, counting from 1. In the second line just output the maximum number of robots that can be placed in that map.

样例

Sample Input

2
4 4
o***
*###
oo#o
***o
4 4
#ooo
o#oo
oo#o
***#

Sample Output

Case :1
3
Case :2
5
数据规模与约定

时间限制: 5 5 5s
空间限制: 32 32 32MB
在这里插入图片描述

题解

可知一连串连在一起的空地必然只可能有一个机器人的存在,所以我们可以把连在一起的空地看做一个点,横着做一遍,竖着做一遍,显然,如果一个点同时属于一片横着的空地和一篇竖着的空地,就只可能存在其一,这时候运用匈牙利算法即可。
在这里插入图片描述

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 55;

int n, m, ans, cntx, cnty, T;
char ch[N][N];
bool vis[N*N];
vector< int > e[N*N];
int x[N][N], y[N][N], cp[N*N];

inline void P(){memset(vis, 0, sizeof vis);}
inline void clea() {
	memset(cp , 0, sizeof cp);
	memset(x, 0, sizeof x);
	memset(y, 0, sizeof y);
	ans = cntx = cnty = 0;
}
inline void init() {
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++) {
		int num = 0;
		scanf("%s", ch[i]+1);
		for(int j = 1; j <= m; j++) {
			if(ch[i][j] == 'o') {
				if(!num) ++cntx, num = 1;
				x[i][j] = cntx;
			}
			if(ch[i][j] == '#') num = 0;
		}		
	}//处理横着的空地连片
	for(int j = 1; j <= m; j++) {
		int num = 0;
		for(int i = 1; i <= n; i++) {
			if(ch[i][j] == 'o') {
				if(!num) ++cnty, num = 1;
				y[i][j] = cnty;
			}
			if(ch[i][j] == '#') num = 0;
		}
	}//处理竖着的空地连片
}

inline void work() {
	for(int i = 1; i <= cntx; i++) e[i].clear();//清空
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
			if(x[i][j] && y[i][j])
				e[x[i][j]].push_back(y[i][j]);
	//如果同时属于横着的和竖着的,连边
}

inline int dfs(int k) {
	for(int i = 0; i < e[k].size(); i++) {
		int y = e[k][i];
		if(vis[y]) continue;
		vis[y] = 1;
		if(!cp[y] || dfs(cp[y])) { cp[y] = k; return 1; }
	}
	return 0;
}

inline void find() {
	for(int i = 1; i <= cntx; i++) 
		P(), ans += dfs(i);//跑最大匹配
} 

inline void outo() {
	printf("%d\n", ans);
}

int main() {
	scanf("%d", &T);
	for(int t = 1; t <= T; t++) {
		printf("Case :%d\n", t);
		clea(); init(); work(); find(); outo();
	}
	return 0;
}

在这里插入图片描述
在这里插入图片描述
棋盘覆盖

Description

给出一张n ∗ * n ( n < = 100 ) (n<=100) (n<=100) 的国际象棋棋盘,其中被删除了一些点,问可以使用多少 1 ∗ 2 1*2 12的多米诺骨牌进行掩盖。

Input Format

第一行为n,m(表示有m个删除的格子)
第二行到m+1行为x,y,分别表示删除格子所在的位置
x为第x行
y为第y列

Output Format

一个数,即最大覆盖格数

Sample Input
8 0
Sample Output
32
Limitation

各个测试点 1 s 1s 1s
在这里插入图片描述

题解

黑白黑白
白黑白黑
黑白黑白

我们将整个棋盘如上面的样式涂色,除去删除的点,一个骨牌覆盖一格黑与一个白,这就是经典的二分图匹配问题了。
在这里插入图片描述

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

int n, m, ans;
int a[105][105], cp[10005];
bool vis[10005];
int dx[4] = {0,1}, dy[4] = {1,0};
vector<int> e[10005];

inline int C(int i, int j){return i*n+j;}//二维转一维
inline bool P(int i){return ((0<=i)&&(i<n)) ? 1 : 0;}//存在于棋盘中

inline int dfs(int k) {
	for(int i = 0; i < e[k].size(); i++) {
		int y = e[k][i];
		if(vis[y]) continue; vis[y] = 1;
		if(cp[y] == -1 || dfs(cp[y])) { cp[y] = k; return 1; }
	}
	return 0;
}

int main() {
	scanf("%d%d", &n, &m);
	for(int i = 1, u, v; i <= m; i++) {
		scanf("%d%d", &u, &v);
		a[u-1][v-1] = 1;
	}
	for(int i = 0; i < n; i++)
		for(int j = 0; j < n; j++) 
			if(!a[i][j]) {
				for(int k = 0; k < 2; k++) {//往下往右连边
					int x = i+dx[k], y = j+dy[k];
					if(!a[x][y] && P(x) && P(y)) 
						e[C(i, j)].push_back(C(x, y)),
						e[C(x, y)].push_back(C(i, j));
				}
			}
	memset(cp, -1, sizeof cp);
	for(int i = 0; i < n; i++)
		for(int j = 0; j < n; j++) 
			if((i ^ j) & 1) {//选择一种颜色(横列的奇偶不相同)
				memset(vis, 0, sizeof vis);
				ans += dfs(C(i, j));
			}//跑最大匹配
	printf("%d\n", ans);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
未匹配点:没有对面的点与之匹配
可匹配点:有对面的点与之匹配,但是对面的点也有别的选择
必然匹配点:有对面的点与之匹配,且对面的点仅可以与这点匹配
不难发现,如果一个点在二分图上是必然匹配点,那么先手必胜,否则后手胜。
然后跑一遍最大匹配, 再判断每个点是否是最大匹配点即可。
在这里插入图片描述

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int N = 1e4+10;
struct psx{int next, y;} e[N<<1];
int len, n, m, old, lin[N], cp[N];
bool vis[N];

inline void insert(int xx, int yy) {
	e[++len].next = lin[xx];
	lin[xx] = len;
	e[len].y = yy;
} 

inline int dfs(int k) {
	for(int i = lin[k]; i; i = e[i].next) {
		int y = e[i].y;
		if(!vis[y] && y != old) {
			vis[y] = 1;
			if(!cp[y] || dfs(cp[y])) { cp[y] = k;  cp[k] = y;  return 1; }
		}
	}
	return 0;
}


int main() { 
	scanf("%d%d", &n, &m);
	for(int i = 1, u, v; i <= m; i++) {
		scanf("%d%d", &u, &v);
		insert(u, v);
		insert(v, u);
	}
	for(int i = 1; i <= n; i++)
		if(!cp[i]) memset(vis, 0, sizeof vis), dfs(i);//最大匹配
	for(int i = 1; i <= n; i++) {
		if(!cp[i]) puts("Elephant");//如果是未匹配点
		else {
			int x = cp[i];
			old = i;
			cp[i] = cp[x] = 0;
			memset(vis, 0, sizeof vis);
			if(dfs(x)) puts("Elephant");//如果是可匹配点
			else puts("Hamster"), cp[i] = x, cp[x] = i;//如果是必然匹配点
		}
	}
	return 0;
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值