二部图
二部图的定义:设
G
G
G是一个图,若
V
(
G
)
V(G)
V(G)有一个划分:
V
(
G
)
=
V
1
⋃
V
2
V(G)=V_1 \bigcup V_2
V(G)=V1⋃V2,使得<
V
1
V_1
V1><
V
2
V_2
V2>都是空图,那么称图
G
G
G是一个双图,或二部图,或二分图。
简单来说,就是在图中切上一刀,就能使所有点变成孤立的,如下图沿着红线切一刀。
判定二分图一般使用标号法,即将一个顶点和与之相邻的顶点标为不同,若此过程可以一直进行下去,则为二分图。
代码判定
下面给出深搜的判定,广搜当然也可以(只要维护一个队列)。
#include <iostream>
#include <algorithm>
#include <string.h>
#include <stdio.h>
#include <math.h>
using namespace std;
const int N = 505;
int m,n;
int color[N];
int edge[N][N];
bool dfs(int v, int c){
color[v] = c; //将当前顶点涂色
for(int i = 0; i < n; i++){ //遍历所有相邻顶点,即连着的点
if(edge[v][i] == 1){ //如果顶点存在
if(color[i] == c) //如果颜色重复,就返回false
return false;
if(color[i] == 0 && !dfs(i,-c)) //如果还未涂色,就染上相反的颜色-c,并dfs这个顶点,进入下一层
return false; //返回false
}
}
return true; //如果所有顶点涂完色,并且没有出现同色的相邻顶点,就返回true
}
void solve(){
for(int i = 0; i < n; i++){//对每一点都要判定,因为图可能不是连通的
if(color[i] == 0){
if(!dfs(i, 1)){
printf("NOT BICOLORABLE.\n");
return;
}
}
}
printf("BICOLORABLE.\n");
}
int main(){
int u,v;
while(cin >> n >> m){
memset(color, 0, sizeof(color));
memset(edge, 0, sizeof(edge));
for(int i = 0; i < m; i++){
cin >> u >> v; //因为是无向图,所以要往两个方向添加边
edge[u][v] = 1; //正着添加
edge[v][u] = 1; //反着添加
}
solve();
}
return 0;
}
匈牙利算法
基本概念:
- 图
G
G
G的一个匹配是由一组没有公共端点的不是圈的边构成的集合。
如图就是之前的图的一个匹配。可知:1. 匹配是边的集合;2. 在该集合中,任意两条边不能有共同的顶点。
就好像参加相亲节目,在不考虑同性恋的情况下,要给匹配合适的对象,有边代表男女嘉宾互有好感。所谓最大匹配,就是节目效果要达到最好,需要尽可能多的互有好感的人配对。
匈牙利算法可以解决二部图上最大匹配问题。该算法的核心就是寻找增广路径,它是一种用增广路径求二分图最大匹配的算法。
——上面的描述看起来很绕而且有很多概念对不对,下面我们直观地感受一下算法的过程。你会发现,其实我们做的是月老的活。
首先,我们这个相亲节目在先来后到的基础上很照顾后来的人,会优先考虑后来人的需求,但同时也不能拆散先来的人让他遗憾离场。
现在有三个男嘉宾和三个女嘉宾,男嘉宾有选择权。先从一号男嘉宾开始,他选择了1号女嘉宾,3号女嘉宾成为了他的备胎。
2号男嘉宾来了,他说我也要1号女嘉宾,1号男嘉宾你往后稍稍。节目组这时候劝说1号男嘉宾,哎,你还有3号备胎,你把1号女嘉宾让给2号男嘉宾吧!
1号一想也是,不如顺便送个人情,于是他换成了3号女嘉宾,2号男嘉宾如愿匹配了1号女嘉宾,并把2号女嘉宾作为了自己的备胎。
这时3号男嘉宾上场,他说我为1号而来!我只要1号!节目组的老好人们又去劝说正和1号牵线的2号男嘉宾。当然2号男嘉宾也同意了,毕竟他还有2号备胎嘛!
于是本期节目皆大欢喜,大家都牵手成功~
——这个匹配即是最大匹配,也是完美匹配。
但是有没有失败而归的情况呢?也是有的:
假如是这样一张图:
1号2号男嘉宾已经进行了协调和选择:
这时候3号男嘉宾来了,还是非1号不可。节目组劝说2号,2号说我要看看备胎在不在,于是询问3号女嘉宾。
3号女嘉宾说我得问问1号男嘉宾还有没有备胎。(这是一个递归的过程)
1号男嘉宾:你就是我的备胎!我的原配已经给2号了!
于是3号女嘉宾告诉2号,对不起啊,1号没有选择了,我也不当你的备胎了。
于是2号男嘉宾告诉节目组,对不起啊,我也没有选择了,我不让。
节目组当然尊重他们的选择,毕竟不能强行拆散,只为了3号男嘉宾。所以节目组告诉3号男嘉宾,对不起啊,你没有牵手成功。
匹配结果如下:
这是一个最大匹配(满足多数人的需求),但不是完美匹配。
实现代码
题例:POJ1274
大意:有n个槽和m头牛,牛只在特定的几个槽才产奶,求最多几头牛可以产奶。
#include <stdio.h>
#include <iostream>
#include <string.h>
using namespace std;
const int maxn = 220;
int n,m;
int mark[maxn];
int match[maxn];
int maze[maxn][maxn];
int ans;
bool Find(int x){
for(int i = 1;i <= m;++i){
if(maze[x][i] && !mark[i]){ //如果牛在这里产奶且槽还没问过
mark[i] = 1;
if(match[i] == 0 || Find(match[i])){//如果没分配或者分配了但是可以找到别的给那个对象
match[i] = x;
return true;
}
}
}
return false;
}
void slove(){
memset(match,0,sizeof(match));
ans = 0;
for(int i = 1; i <= n;++i){
memset(mark,0,sizeof(mark));
if(Find(i))ans++;
}
}
int main()
{
while(~scanf("%d%d",&n,&m)){
for(int i = 1; i <= n;++i)
for(int j = 1; j <= m;++j)
maze[i][j] = 0;
for(int i = 1; i <= n;++i){
int x;
scanf("%d",&x);
for(int j = 1; j <= x;++j){
int tmp;
scanf("%d",&tmp);
maze[i][tmp] = 1;
}
}
slove();
printf("%d\n",ans);
}
return 0;
}
最大匹配数=最小点覆盖数。因此最小点覆盖也可以转化为最大匹配问题。
一些例题:
POJ1325
POJ3692(需要建补图,是最大独立集的问题)
POJ1469
POJ3041*这个问题的转化比较巧妙了,题目大意是一个网格里有一些障碍物,一发炮弹可以清除同一行或者同一列的障碍物,问最少开几枪。转化障碍物坐标,行坐标为一个集合,列坐标为一个集合,障碍物的坐标连线,使用匈牙利算法。
(大概是考虑在同一行则行坐标相等,尽可能多的为不同的行坐标都能匹配到列坐标。。。)
应用可以参考这篇博客:匈牙利算法—介绍与基本用途