二分图:一个无向图的顶点可以分割成两个互不相交的顶点集称为二分图。
若全部的点都匹配了称为完美匹配;
存在一条y1-x1-y2-x2(如图)的路径则称交替路径。
若路径的起点和终点未被匹配则存在一条增广路径。
则可使匹配数增加 1 ;
berge定理:若匹配M为最大匹配当且仅当二分图中不存在增广路径
/* **************************************************************************
//二分图匹配(匈牙利算法的DFS实现)
//初始化:g[][]两边顶点的划分情况
//建立g[i][j]表示i->j的有向边就可以了,是左边向右边的匹配
//g没有边相连则初始化为0
//uN是匹配左边的顶点数,vN是匹配右边的顶点数
//调用:res=hungary();输出最大匹配数
//优点:适用于稠密图,DFS找增广路,实现简洁易于理解
//时间复杂度:O(VE)
//***************************************************************************/
//顶点编号从0开始的
const int MAXN=510;
int uN,vN;//u,v数目
int g[MAXN][MAXN];
int linker[MAXN];
bool used[MAXN];
bool dfs(int u)//从左边开始找增广路径
{
int v;
for(v=0;v<vN;v++)//这个顶点编号从0开始,若要从1开始需要修改
if(g[u][v]&&!used[v])
{
used[v]=true;
if(linker[v]==-1||dfs(linker[v]))
{//找增广路,反向
linker[v]=u;
return true;
}
}
return false;//这个不要忘了,经常忘记这句
}
int hungary()
{
int res=0;
int u;
memset(linker,-1,sizeof(linker));
for(u=0;u<uN;u++)
{
memset(used,0,sizeof(used));
if(dfs(u)) res++;
}
return res;
}
/* *********************************************
二分图匹配(Hopcroft-Carp的算法)。
初始化:g[][]邻接矩阵
调用:res=MaxMatch(); Nx,Ny要初始化!!!
时间复杂大为 O(V^0.5 E)
适用于数据较大的二分匹配
需要queue头文件
********************************************** */
const int MAXN=3000;
const int INF=1<<28;
int g[MAXN][MAXN],Mx[MAXN],My[MAXN],Nx,Ny;
int dx[MAXN],dy[MAXN],dis;
bool vst[MAXN];
bool searchP()
{
queue<int>Q;
dis=INF;
memset(dx,-1,sizeof(dx));
memset(dy,-1,sizeof(dy));
for(int i=0;i<Nx;i++)
if(Mx[i]==-1)
{
Q.push(i);
dx[i]=0;
}
while(!Q.empty())
{
int u=Q.front();
Q.pop();
if(dx[u]>dis) break;
for(int v=0;v<Ny;v++)
if(g[u][v]&&dy[v]==-1)
{
dy[v]=dx[u]+1;
if(My[v]==-1) dis=dy[v];
else
{
dx[My[v]]=dy[v]+1;
Q.push(My[v]);
}
}
}
return dis!=INF;
}
bool DFS(int u)
{
for(int v=0;v<Ny;v++)
if(!vst[v]&&g[u][v]&&dy[v]==dx[u]+1)
{
vst[v]=1;
if(My[v]!=-1&&dy[v]==dis) continue;
if(My[v]==-1||DFS(My[v]))
{
My[v]=u;
Mx[u]=v;
return 1;
}
}
return 0;
}
int MaxMatch()
{
int res=0;
memset(Mx,-1,sizeof(Mx));
memset(My,-1,sizeof(My));
while(searchP())
{
memset(vst,0,sizeof(vst));
for(int i=0;i<Nx;i++)
if(Mx[i]==-1&&DFS(i)) res++;
}
return res;
}
最小路径覆盖数= 顶点数-最大匹配数
例题 HDU1151
题目大意:在一个城镇,有m个路口,和n条路,这些路都是单向的,而且路不会形成环,现在要弄一些伞兵去巡查这个城镇,伞兵只能沿着路的方向走,问最少需要多少伞兵才能把所有的路口搜一遍。
(其实就是给一个m个点n条边的有向无环图,求该图的最小路径覆盖)
#include<stdio.h>
#include<string.h>
#define N 202
int use[N]; //记录y中节点是否使用
int link[N]; //记录当前与y节点相连的x的节点
int mat[N][N]; //记录连接x和y的边,如果i和j之间有边则为1,否则为0
int gn,gm; //二分图中x和y中点的数目
int can(int t)
{
int j;
for(j=1;j<=gn;j++)
{
if(use[j]==0 && mat[t][j])//y中 第j个点未被使用.且有边
{
use[j]=1;//标记
if(link[j]==0 || can(link[j]))//如果j点没有连接则连接就t点
{ //有连接则重新将连j的上一点link[j] 重新查找边、因为已经标记j点
link[j]=t; // link[j] 连接下一个相连的点;
return 1;
}
}
}
return 0;
}
int MaxMatch()
{
int i,num;
num=0;
memset(link,0,sizeof(link));
for(i=1;i<=gn;i++)//x中 第i个点
{
memset(use,0,sizeof(use));
if(can(i)) num++;
}
return num;
}
void init()
{
memset(mat,0,sizeof(mat));
}
int main()
{
int t,k;
scanf("%d",&t);
while(t--)
{
init();
scanf("%d%d",&gn,&k);
while(k--)
{
int a,b;
scanf("%d%d",&a,&b);
mat[a][b]=1;
}
printf("%d\n",gn-MaxMatch());
}
return 0;
}
/*******************************************
最大匹配数:匈牙利算法
HDU 1281
车不能相互攻击则 坐标(x,y)上x只能放一个棋子,y上只能放一个棋子
所以一个x对应着一个y,其他棋子不能放在x上或者y上.
这样就是二分匹配了
重要点:则是不可替代的点,所以拿去这个点看最大匹配数有无减少
********************************************/
#include<stdio.h>
#include<string.h>
#define N 202
int use[N]; //记录y中节点是否使用
int link[N]; //记录当前与y节点相连的x的节点
int mat[N][N]; //记录连接x和y的边,如果i和j之间有边则为1,否则为0
int gn,gm; //二分图中x和y中点的数目
int can(int t)
{
int j;
for(j=1;j<=gn;j++)
{
if(use[j]==0 && mat[t][j])//y中 第j个点未被使用.且有边
{
use[j]=1;//标记
if(link[j]==0 || can(link[j]))//如果j点没有连接则连接就t点
{ //有连接则重新将连j的上一点link[j] 重新查找边、因为已经标记j点
link[j]=t; // link[j] 连接下一个相连的点;
return 1;
}
}
}
return 0;
}
int MaxMatch()
{
int i,num;
num=0;
memset(link,0,sizeof(link));
for(i=1;i<=gn;i++)//x中 第i个点
{
memset(use,0,sizeof(use));
if(can(i)) num++;
}
return num;
}
void init(){
memset(mat,0,sizeof(mat));
}
int main()
{
int n,m,k,cas=1,x,y;
while(~scanf("%d%d%d",&n,&m,&k)){
gn=n;
gm=m;
init();
for(int i=0;i<k;i++){
scanf("%d%d",&x,&y);
mat[x][y]=1;
}
int ans=MaxMatch(),sum=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(mat[i][j]){
mat[i][j]=0;
if(MaxMatch()!=ans)
sum++;
mat[i][j]=1;
}
}
}
printf("Board %d have %d important blanks for %d chessmen.\n",cas++,sum,ans);
}
return 0;
}
/*******************************************
最大匹配数:匈牙利算法
HDU 1498
题意:给出一个矩阵 一次可以消除一行或者一列的同种颜色的气球;
如果可以在k次之内不能消完这种颜色的气球 则记录下;
********************************************/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define N 202
int use[N]; //记录y中节点是否使用
int link[N]; //记录当前与y节点相连的x的节点
int mat[N][N]; //记录连接x和y的边,如果i和j之间有边则为1,否则为0
int gn,gm; //二分图中x和y中点的数目
int can(int t)
{
int j;
for(j=1;j<=gn;j++)
{
if(use[j]==0 && mat[t][j])//y中 第j个点未被使用.且有边
{
use[j]=1;//标记
if(link[j]==0 || can(link[j]))//如果j点没有连接则连接就t点
{ //有连接则重新将连j的上一点link[j] 重新查找边、因为已经标记j点
link[j]=t; // link[j] 连接下一个相连的点;
return 1;
}
}
}
return 0;
}
int MaxMatch()
{
int i,num;
num=0;
memset(link,0,sizeof(link));
for(i=1;i<=gn;i++)//x中 第i个点
{
memset(use,0,sizeof(use));
if(can(i))
num++;
}
return num;
}
void init(){
memset(mat,0,sizeof(mat));
}
int main()
{
int n,k,i,j,l;
int map[N][N];
while(scanf("%d%d",&n,&k),n+k){
int ans[N]={0},in[N*N+11],num1=0,num2=0;
bool vis[N*N];
memset(vis,false,sizeof(vis));
memset(map,0,sizeof(map));
for(i=1;i<=n;i++)
{
for( j=1;j<=n;j++){
scanf("%d",&map[i][j]); //输入矩阵
if(!vis[map[i][j]]){
in[num1++]=map[i][j]; //记录出现的气球颜色并记录
vis[map[i][j]]=true;
}
}
}
gn=gm=n;
for(i=0;i<num1;i++)
{
init();
for(j=1;j<=n;j++) //建二分图
{
for(l=1;l<=n;l++)
{
if(map[j][l]==in[i])
mat[j][l]=1;
}
}
if(k<MaxMatch()) //如果匹配数大于k次则记录
ans[num2++]=in[i];
}
sort(ans,ans+num2);//输出要排序
if(num2==0)
printf("-1\n");
for(i=0;i<num2;i++)
printf("%d%c",ans[i],i==num2-1?'\n':' ');
}
return 0;
}
/*******************************************
最大匹配数:匈牙利算法
HDU 1507
题意:最大匹配
给出一些不能放的格子 剩下的放置 2x1的格子
问最大多少可以放几个2x1格子并输出
********************************************/
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string.h>
#include <vector>
using namespace std;
const int MAXN = 510;
int uN,vN;//u,v的数目,使用前面必须赋值
int g[MAXN][MAXN];//邻接矩阵
int linker[MAXN];
bool used[MAXN];
bool dfs(int u)
{
for(int v = 0; v < vN;v++)
if(g[u][v] && !used[v])
{
used[v] = true;
if(linker[v] == -1 || dfs(linker[v]))
{
linker[v] = u;
return true;
}
}
return false;
}
int MaxMatch()
{
int res = 0;
memset(linker,-1,sizeof(linker));
for(int u = 0;u < uN;u++){
memset(used,false,sizeof(used));
if(dfs(u))res++;
}
return res;
}
int a[110][110];
int b[100];
int main()
{
int n,m,k;
int u,v;
while(scanf("%d%d",&n,&m),n+m)
{
scanf("%d",&k);
memset(a,0,sizeof(a));
while(k--){
scanf("%d%d",&u,&v);
u--,v--;
a[u][v]=-1;
}
int index = 0;
for(int i = 0;i < n;i++){
for(int j = 0;j < m;j++)
if(a[i][j]!=-1){
b[index] = i*m + j;
a[i][j] = index++;
}
}
uN = vN = index;
memset(g,0,sizeof(g));
for(int i = 0;i < n;i++){//建立匹配图
for(int j= 0;j < m;j++){
if(a[i][j]!=-1 && (i+j)%2==1){
u = a[i][j];
if(i > 0 && a[i-1][j]!=-1)
g[u][a[i-1][j]]=1;
if(i < n-1 && a[i+1][j]!=-1)
g[u][a[i+1][j]]=1;
if(j > 0 && a[i][j-1]!=-1)
g[u][a[i][j-1]]=1;
if(j < m-1 && a[i][j+1]!=-1)
g[u][a[i][j+1]]=1;
}
}
}
int ans = MaxMatch();
printf("%d\n",ans);
for(int i = 0;i <vN;i++){
if(linker[i]!=-1){
int x1 = b[i]/m;
int y1 = b[i]%m;
int x2 = b[linker[i]]/m;
int y2 = b[linker[i]]%m;
printf("(%d,%d)--(%d,%d)\n",x1+1,y1+1,x2+1,y2+1);
}
}
printf("\n");
}
return 0;
}