二分图
知识梳理
概念
如果一张无向图的 个节点,可以分成 两个非空集合,其中A B ,并且同一集合内的点都没有边相连,那么称这张无向图为二分图
二分图判定
一张图是二分图,当且仅当图中不存在奇环(长度为奇数的环)
用染色法判定此图是否为二分图
尝试用黑白两色标记图中节点,当一个节点被标记,它的所有相邻节点应该被标记为另一种颜色
若标记过程产生冲突,则说明存在奇环
void dfs(int x, int color) {
v[x] = color;
遍历所有与x相连的无向边
if(!v[y]) dfs(y, 3-color);
else if(v[y] = color) 判定该无向图不是二分图,算法结束
}
二分图最大匹配
任意两条边没有公共端点 的边的集合被称为图的一组匹配
在二分图中,包含边数最多的一组匹配被称为 二分图的最大匹配
任意一组匹配,属于 的边被称为匹配边,另外的称为非匹配边, 匹配边的端点被称为匹配点,其他节点被称为非匹配点
例题
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.
翻译
给定一 个大小的地图方格,有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
数据规模与约定
时间限制:s
空间限制:MB
题解
可知一连串连在一起的空地必然只可能有一个机器人的存在,所以我们可以把连在一起的空地看做一个点,横着做一遍,竖着做一遍,显然,如果一个点同时属于一片横着的空地和一篇竖着的空地,就只可能存在其一,这时候运用匈牙利算法即可。
代码
#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() {
freopen("data.in", "r", stdin);
freopen("data.out", "w", stdout);
scanf("%d", &T);
for(int t = 1; t <= T; t++) {
printf("Case :%d\n", t);
clea();
init();
work();
find();
outo();
}
return 0;
}
棋盘覆盖
Description
给出一张nn 的国际象棋棋盘,其中被删除了一些点,问可以使用多少的多米诺骨牌进行掩盖。
Input Format
第一行为n,m(表示有m个删除的格子)
第二行到m+1行为x,y,分别表示删除格子所在的位置
x为第x行
y为第y列
Output Format
一个数,即最大覆盖格数
Sample Input
8 0
Sample Output
32
Limitation
各个测试点
题解
黑白黑白
白黑白黑
黑白黑白
我们将整个棋盘如上面的样式涂色,除去删除的点,一个骨牌覆盖一格黑与一个白,这就是经典的二分图匹配问题了。
代码
#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]) {
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;
}