二分匹配 从陌生到认知

参考 http://blog.csdn.net/q3498233/article/details/5786225

二分图:二分图是这样一个图,它的顶点可以分类两个集合X和Y,所有的边关联的两个顶点恰好一个属于集合X,另一个属于集合Y。

二分图匹配:给定一个二分图G,在G的一个子图M中,M的边集中的任意两条边都不依附于同一个顶点,则称M是一个匹配。
最大匹配:图中包含边数最多的匹配称为图的最大匹配。
完美匹配:如果所有点都在匹配边上,则称这个最大匹配是完美匹配。

二分图匹配基本概念:

未盖点
设VI是G的一个顶点,如果VI不与任意一条属于匹配M的边相关联,就称VI是一个未盖点。
交错轨
   设P是图G的一条轨,如果P的任意两条相邻的边一定是一条属于M而另一条不属于M,就称P是交错轨。
可增广轨(增广路)
    两个端点都是未盖点的交错轨称为可增广轨。

可增广轨的性质:

1:P的路径长度必定为奇数,第一条边和最后一条边都不属于M。
2:P经过取反操作可以得到一个更大的匹配M’。
3:M为G的最大匹配当且仅当不存在相对于M的增广路径。

二分图最大匹配匈牙利算法:

算法的思路是不停的找增广轨,并增加匹配的个数,增广轨顾名思义是指一条可以使匹配数变多的路径,在匹配问题中,增广轨的表现形式是一条"交错轨",也就是说这条由图的边组成的路径,它的第一条边是目前还没有参与匹配的,第二条边参与了匹配,第三条边没有..最后一条边没有参与匹配,并且始点和终点还没有被选择过.这样交错进行,显然他有奇数条边.那么对于这样一条路径,我们可以将第一条边改为已匹配,第二条边改为未匹配...以此类推.也就是将所有的边进行"取反",容易发现这样修改以后,匹配仍然是合法的,但是匹配数增加了一对.另外,单独的一条连接两个未匹配点的边显然也是交错轨.可以证明,当不能再找到增广轨时,就得到了一个最大匹配.这也就是匈牙利算法的思路。

代码:


输入的时候可以1个对应多个,而寻找到的最大匹配中任意2个边是没有公共交点的

模板

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;
const int MAXN=11111;//根据点的个数变
int linker[MAXN];
bool used[MAXN];
vector<int>map[MAXN];
int uN;
bool dfs(int u)
{    
    for(int i=0;i<map[u].size();i++)        
    {        
        if(!used[map[u][i]])            
        {            
            used[map[u][i]]=true;            
            if(linker[map[u][i]]==-1||dfs(linker[map[u][i]]))                
            {                
                linker[map[u][i]]=u;                
                return true;            
            }            
        }        
    }    
    return false;    
}
int hungary()
{    
    int u;    
    int res=0;    
    memset(linker,-1,sizeof(linker));    
    for(u=0;u<uN;u++)        
    {        
        memset(used,false,sizeof(used));        
        if(dfs(u)) res++;        
    }    
    return res;    
}
int main()
{
    int u,k,v,i;    
    int n,m;    
    while(scanf("%d %d",&n,&m)!=EOF)//n个点  m条边
    {        
        for( i=0;i<MAXN;i++)            
            map[i].clear();
        for(i=0;i<m;i++)
        {
            scanf("%d %d",&v,&u);   
            v=v-1;//如果点是从1开始计数的 要减1  如果从0开始去掉这句以及下面那句
            u=u-1;
            map[u].push_back(v);        
            map[v].push_back(u);    
        }
        uN=n;    
        printf("%d\n",hungary()/2);    
    }    
    return 0;    
}




真正求二分图的最大匹配的题目很少,往往做一些简单的变化:

变种1:二分图的最小顶点覆盖

最小顶点覆盖要求用最少的点(X或Y中都行),让每条边都至少和其中一个点关联。

knoig定理:二分图的最小顶点覆盖数 = 二分图的最大匹配数(m)。

变种2:DAG图的最小路径覆盖

用尽量少的不相交简单路径覆盖有向无环图(DAG)G的所有顶点,这就是DAG图的最小路径覆盖问题。

结论:DAG图的最小路径覆盖数 = 节点数(n)- 最大匹配数(m)

变种3:二分图的最大独立集

结论:二分图的最大独立集数 = 节点数(n)— 最大匹配数(m)

最大独立集相当于问:在N个点的图G中选出m个点,使这m个点两两之间没有边.求m最大值


当我们遇到的题目中可以将数据分成2个集合的时候就应该敏感性的想到二分匹配


最大独立集例题 :

最大独立集通常采用互斥的关系建立边    

用二分匹配找出所有的互斥的关系个数 用总数n一减就是最大独立集的个数

hdu 3829 二分图最大独立集

//Cat VS Dog.cpp : 定义控制台应用程序的入口点。
//
/*
题目描述:动物园有N只猫,M只狗,P个小孩。每个小孩都有自己喜欢的动物和讨厌的动物,如果他喜欢狗,那么就讨厌猫,
如果他讨厌猫,那么他就喜欢狗。某个小孩能开心,当且仅当他喜欢的动物留在动物园和讨厌的动物不在动物园里面。
现让管理员通过带走某些动物,问最多能使多少个孩子开心。
解题思路:题目有一个关键点,孩子喜欢猫,必然不喜欢狗,反之。 即猫和猫之间,狗和狗之间一定不存在矛盾关系,符合二分图的概念。
如何建立二分图:
若甲小孩喜欢的动物与乙小孩讨厌的动物一样,或者甲小孩讨厌的动物与乙小孩喜欢的动物一样,那甲乙之间就存在着排斥关系,则他们之间连接一条边。
建立完二分图之后,相当于求二分图的最大独立集 = 顶点数 - 最大匹配数。
*/
#include "stdafx.h"
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
using namespace std;

const int MAXN = 508;

struct Child
{
	string like;
	string dis;
};

Child cat[MAXN],dog[MAXN];
int map[MAXN][MAXN];
int link[MAXN];
int used[MAXN];
int cat_num;
int dog_num;

int dfs(int k)
{
	for(int i=0; i<dog_num; i++)
	{
		if(!used[i] && map[k][i])
		{
			used[i] = 1;
			if(link[i] == -1 || dfs(link[i]))
			{
				link[i] = k;
				return 1;
			}
		}
	}
	return 0;
}

int maxMatch()
{
	int cnt = 0;
	for(int i=0; i<cat_num; i++)
	{
		memset(used,0,sizeof(used));
		if(dfs(i))
		{
			cnt++;
		}
	}
	return cnt;
}

int main()
{
	int n,m,p;

	string a,b;
	while(cin>>n>>m>>p)
	{
		memset(map,0,sizeof(map));
		memset(link,-1,sizeof(link));
		cat_num = dog_num = 0;
		while(p--)
		{
			cin>>a>>b;
			//将喜欢猫的孩子划为A子集
			if(a[0] == 'C') 
			{
				cat[cat_num].like = a;
				cat[cat_num].dis = b;
				cat_num++;
			}
			//将喜欢狗的孩子划为B子集
			if(a[0] == 'D')
			{
				dog[dog_num].like = a;
				dog[dog_num].dis = b;
				dog_num++;
			}
		}
		for(int i=0; i<cat_num; i++)
		{
			for(int j=0; j<dog_num; j++)
			{
				//若存在排斥关系
				if(cat[i].like == dog[j].dis || cat[i].dis == dog[j].like)
				{
					map[i][j] = 1;
				}
			}
		}
		int cnt = maxMatch();
		//最大独立集 = 顶点数 - 最大匹配数
		cout<<cat_num+dog_num-cnt<<endl;
	}
	return 0;
}

HDU 1068 Girls and Boys(最大独立集) 参考 http://www.2cto.com/kf/201207/141366.html


 /*
    题意:n个同学,一些男女同学会有缘分成为情侣,格式ni:(m) n1 n2 n3表示同学ni有缘与n1,n2,n3成为情侣,求集合中不存在有缘成为情侣的同学的最大同学数。
    题解:
    独立集:图的顶点集的子集,其中任意两点不相邻
    最大独立集 = 顶点数 - 最大匹配数
    但是这道题有一点不同,它没有告诉我们是男生还是女生,所以我们需要进行拆点,把一个学生拆成一个男生和一个女生,然后求出最大匹配后除以2即可。
    */ 
    #include <iostream> 
    using namespace std; 
     
    const int nMax = 500; 
    int n; 
    int map[nMax][nMax]; 
    int link[nMax]; 
    int useif[nMax]; 
     
    bool can(int t) 
    { 
        for(int i = 0; i < n; ++ i) 
        { 
            if(!useif[i] && map[t][i]) 
            { 
                useif[i] = 1; 
                if(link[i] == -1 || can(link[i])) 
                { 
                    link[i] = t; 
                    return 1; 
                } 
            } 
        } 
        return 0; 
    } 
    int main() 
    { 
        //freopen("f://data.in", "r", stdin); 
        while(scanf("%d", &n) != EOF) 
        { 
            memset(map, 0, sizeof(map)); 
            memset(link, -1, sizeof(link)); 
            int u, k, v; 
            for(int i = 0; i < n; ++ i) 
            { 
                scanf("%d: (%d)", &u, &k); 
                while(k --) 
                { 
                    scanf("%d", &v); 
                    map[u][v] = 1; 
                } 
            } 
            int num = 0; 
            for(int i = 0; i < n; ++ i) 
            { 
                memset(useif, 0, sizeof(useif)); 
                if(can(i)) ++ num; 
            } 
            printf("%d\n", n - num / 2); 
        } 
        return 0; 
    } 


最小路径覆盖

 

路径覆盖的定义是:在有向图中找一些路径,使之覆盖了图中的所有顶点,就是任意一个顶点都跟那些路径中的某一条相关联,且任何一个顶点有且只有一条路径与之关联,一个单独的顶点是一条路径.最小路径覆盖就是最少的路径覆盖

注意:每个点的入度出度都是1   也就是说 每个点决定1条边

 

通常一些可以建立类似 “从a转化到b的类型的题目 ” 可以用最小路径覆盖      即从a可以通过条件到达b   则可以用(个人理解而已)

因为 可以假设一开始 所有的点都是不连通的  如果需要联通 就需要n条边   之后用二分匹配每找到一条边(二分匹配是寻找包含边数最多 且每2条边没有交点) 就减少了一条边  假设找到了最多的边数为m  则还需要n-m条路径  所以最小路径覆盖=n-二分匹配。 

以上为本人个人理解  不知道是否有误。  


poj 1422

题意:一个地图上有n个小镇,以及连接着其中两个小镇的有向边,而且这些边无法形成回路。现在选择一些小镇空降士兵(1个小镇最多1个士兵),士兵能沿着边走到尽头,问最少空降几个士兵,能遍历完所有的小镇。


思路:匈牙利算法求最小路径覆盖:在一个有向图中,路径覆盖就是在图中找一些路径,使之覆盖了图中的所有顶点,且任何一个顶点有且只有一条路径与之关联;(如果把这些路径中的每条路径从它的起始点走到它的终点,那么恰好可以经过图中的每个顶点一次且仅一次);解决此类问题可以建立一个二分图模型。把所有顶点i拆成两个:X结点集中的i和Y结点集中的i',如果有边i->j,则在二分图中引入边i->j',设二分图最大匹配为m,则结果就是n-m

我们可以把问题转化为,在图上的边中选出一些边,使得每个点的如度与出度都不超过1。

我们开始在图上的每个点都放上伞兵,然后没选出一条边,就意味着有一个伞兵可以被取消掉了。也就是说需要的最少伞兵数=点总数-能选出的最大边数。

我们只要求最大边数即可。用二分图匹配,把每个点拆成两个点,分别加入二分图的两个点集,原图中一条由a到b的边在二分图中是一条由第一个点集中的第a个点到第二个点集中的第b个点。也就是第一个点集的点与出边有关,第二个与入边有关。匹配时也就保证了每个点的如度与出度都不超过1。求最大匹配即为能选出的最大边数。

/*

*/
#include<iostream>
using namespace std;
#define MAXV 130

int n,m;				//交叉路口和街道的个数
int map[MAXV][MAXV];
int link[MAXV],use[MAXV];

bool dfs(int x){
	int i,j;
	for(i=1;i<=n;i++){
		if(map[x][i] && !use[i] ){		//拆点注意i不能等于x,否则就自己和自己匹配了
			j=link[i];
			use[i]=1;
			link[i]=x;
			if(j==-1 || dfs(j)) return true;
			link[i]=j;
		}
	}
	return false;
}

int hungary(){
	int num=0;
	int i;
	memset(link,-1,sizeof(link));
	for(i=1;i<=n;i++){
		memset(use,0,sizeof(use));
		if(dfs(i)) num++;
	}
	return n-num;
}

int main(){
	int Case;
	int a,b,i;
	scanf("%d",&Case);
	while(Case--){
		scanf("%d%d",&n,&m);
		memset(map,0,sizeof(map));
		for(i=0;i<m;i++){
			scanf("%d%d",&a,&b);
			map[a][b]=1;
		}
		
		printf("%d\n",hungary());
	}
	return 0;
}

/*题目大意:在一个坐标图中有一些点需要覆盖,每在一个点上覆盖就可在覆盖它上下左右4个点中任一个,问最小放几个。
分析:利用黑白染色法把每一个点都和与之相邻的4个点连边,就构成了一个二分图。要求的就是有最小的点数覆盖全部边,即求最小路径覆盖=最大独立集=所有点-最大匹配由此可以求出最优解。实现方法——匈牙利算法即可。注意的是,这里的点是所有可以放得点*2,而匹配数也是正常匹配数的二倍(A到B连了,B到A也连了),所以最后是n-最大匹配/2。
代码:
*/
#include<iostream>
using namespace std;
int h[41][11];
int g[405][405],visit[405],match[405];
char map[41][11];
int d[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
int n,m,ans,sum;
int find(int a)
{
    int i,j;
    for(i=1;i<=sum;i++)
    {
      if(!visit[i]&&g[a][i]==1)
      {
        visit[i]=1;
        if(match[i]==0||find(match[i]))
        {
           match[i]=a;
           return 1;
        }
      }
    }
    return 0;
}
         
int main()
{
    int t,i,j,k,xx,yy;
    cin>>t;
    while(t--)
    {
      sum=ans=0;
      memset(g,0,sizeof(g));
      memset(match,0,sizeof(match));
      cin>>n>>m;
      for(i=0;i<n;i++)
      {
         cin>>map[i];
        for(j=0;j<m;j++)
        {
          if(map[i][j]=='*')
          {
             sum++;
             h[i][j]=sum;
          }
        }
      }
      for(i=0;i<n;i++)
        for(j=0;j<m;j++)
        {
          if(map[i][j]=='*')
          {
           for(k=0;k<=3;k++)
           {
            xx=i+d[k][0];
            yy=j+d[k][1];
            if(xx>=0&&yy>=0&&xx<n&&yy<m&&map[xx][yy]=='*')
                g[h[i][j]][h[xx][yy]]=1;
            }
           }
        }
        for(i=1;i<=sum;i++)
        {
          memset(visit,0,sizeof(visit));
          if(find(i)) ans++;
        }
        cout<<sum-ans/2<<endl;
      }
      return 0;
}




题目大意:给出N(1000)个数字(<2^63 -1),从n中取出k个数,使得任意a,b属于k,a,b不形成整除关系。问k最大是多少

算法:有向图最小路径覆盖数

思路:因为要求取出一些点,使得他们之间没有整除关系,很容易想到利用整除关系建立一个图,然后看最多有多少个点能不相连,如果把图看作无向图,那么就很难想到做法,至少无向图最大点独立集是不能在1000的规模下运行的。如果ab的约束,我们建立一条ab的有向边,最后发现,要求的其实就是最小路径覆盖数。

最小路径覆盖数:在一个有向图中至少放几个人才能走到所有点(延边走,人可以放在任意位置,非官方解释)。

最小路径覆盖:在一个有向图中,最少用几条路径能把所有的点覆盖(路径不交叉,即每个点只属于一条路)。

当有向图无环时,可以用二分图最大匹配来求解。

最少路径覆盖=点数二分图最大匹配

代码略


POJ2060

题目大意:有m个任务,每个任务有开始的时间(xx:xx),起点(a,b)与终点(c,d)
然后做完这个任务的时间是|a-c|+|b-d|分钟,如果一个车子在一天(00:00 to 23:59)之内做完i任务之后又能
做j任务,那么这个车子就做2个任务,依次类推下去
问最少需要多少辆车子把所有任务都做完

 


解题思路:最小路径覆盖
建图:i的开始时间+做完i任务的时间+做完i任务的终点到j任务的起点的时间<j开始的时间
那么做完i又能做完j,则i指向j

然后求最小路径覆盖即可

代码略


zoj 2521 LED Display

题意:七段数码显示器,一个数码上的7段灯可以选择灭掉一些后重复使用这个数码,
但灭掉的段不能再亮了。比如 6 可以 灭掉左小角的一段变成 5 来使用。
但自己不能重复使用,即最少灭一段才能再次使用。 现在按次序给出
一个要现实的数码序列,求需要的最少的灯数,使得能按所给的次序显示所有数码。
分析:
先确定所有数码可以通过灭掉一些段后形成的新数码:
原数码: 它可以形成的数码
0 : 1 7
1 :
2 :
3 : 1 7
4 : 1
5 :
6 : 5
7 : 1
8 : 0 1 2 3 4 5 6 7 9
9 : 1 3 4 5 7
对于输入的n长的序列,对第i个数,如果他后面的数j,可以由i形成,那么连一条有向边

i-j
那么问题转化会在一个有向无环图上找最少的路径数,有且仅有一次经过所有点。
而这个就是经典的最小路径覆盖问题了,可以用最大匹配来做,最小路径数 = n - 最大匹


POJ1548

【题意】

在一个矩形区域内,机器人从左上角出发,每次只能沿着现在位置的下方和右方移动,在移动过程中收拾垃圾,一直到区域的右下角,现在给出所有垃圾的位置,求解最少需要多少个机器人才能将这些垃圾清理成功.

【思路】

建图:以垃圾为点建立二分图,如果后一个垃圾的位置可以通过前一个垃圾的位置可达,则建立一条边,从而形成二分图,求二分图的最小路径覆盖问题。




最小顶点覆盖


poj3041

题目 棋盘上有N个点,每次可以拿走一行或者一列。  问最少多少次可以把棋盘上的所有点都拿走


思路  :

有向图最小路径覆盖数

将行作为左边的点,列作为右边的点,原图中的每个点形成一条边,将代表其行和列的点连接起来。
对已经建好的图求最大匹配

因为要么是对行操作 要么对列操作     分成2个集合    找出最大的匹配数即可


未完待续




你可能感兴趣的:(二分匹配 从陌生到认知)