二分图匹配

来源:互联网 发布:sql 安全策略 编辑:程序博客网 时间:2024/04/28 03:12
        

      二分图指的是这样一种图,其所有顶点可以分成两个集合X和Y,其中X或Y中任意两个在同一集合中的点都不相连,所有的边关联在两个顶点中,恰好一个属于集合X,另一个属于集合Y。给定一个二分图G,M为G边集的一个子集,如果M满足当中的任意两条边都不依附于同一个顶点,则称M是一个匹配。图中包含边数最多的匹配称为图的最大匹配。

     二分图的最大匹配有两种求法,第一种是最大流;第二种就是我现在要讲的匈牙利算法。这个算法说白了就是最大流的算法,但是它跟据二分图匹配这个问题的特点,把最大流算法做了简化,提高了效率。

      增广路径的定义(也称增广轨或交错轨):
  若P是图G中一条连通两个未匹配顶点的路径,并且属M的边和不属M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径。
由增广路径的定义可以推出下述4个结论:
        1-P的路径长度必定为奇数,第一条边和最后一条边都不属于M。
        2-P上所有第奇数条边都不在M中,所有第偶数条边都出现在M中。
   3-P经过取反操作可以得到一个更大的匹配M’。所谓“取反”即把P上所有第奇数条边(原不在M中)加入到M中,并把P中所有第偶数条边(原在M中)从M中删除,则新的匹配数就比原匹配数多了1个。(增广路顾名思义就是使匹配数增多的路径)
        4-M为G的最大匹配当且仅当不存在相对于M的增广路径。

   最大流算法的核心问题就是找增广路径(augment path)。匈牙利算法也不例外,它的基本模式就是:
     初始时最大匹配为空
      while 找得到增广路径
               do 把增广路径加入到最大匹配中去
      可见和最大流算法是一样的。但是这里的增广路径就有它一定的特殊性。(注:匈牙利算法虽然根本上是最大流算法,但是它不需要建网络模型,所以图中不再需要源点和汇点,仅仅是一个二分图。每条边也不需要有方向。)

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

3个重要结论:

最小点覆盖数: 最小覆盖要求用最少的点(X集合或Y集合的都行)让每条边都至少和其中一个点关联。可以证明:

      最少的点(即覆盖数)=最大匹配数
最小路径覆盖:用尽量少的不相交简单路径覆盖有向无环图G的所有结点。

     最小路径覆盖=|N|-最大匹配数
解决此类问题可以建立一个二分图模型。把所有顶点i拆成两个:X结点集中的i和Y结点集中的i',如果有边i->j,则在二分图中引入边i->j',设二分图最大匹配为m,则结果就是n-m。

最大独立集:在N个点的图G中选出m个点,使这m个点两两之间没有边,求m最大值。
二分图最大独立集=顶点数-二分图最大匹配
如果图G满足二分图条件,则可以用二分图匹配来做.最大独立集点数 = N - 最大匹配数。


poj 3216

      题意:给出一个q*q的矩阵和m个任务,矩阵中g[i][j] 表示从i点到j点所需时间(总共q个点),每个任务给p,t,d三个整数,表示要在t时间前到达p点,去完成一个持续d时间的任务,现在派人出去完成这些任务,要求输出最少需要派出多少人。要注意的是:在做一个任务的过程中不能去做其它任务,即一个任务完成了才能去做其它任务,且在某个点完成任务后,其它点没有任务并且该点在以后某个时间还会有任务,则可以在该点等到该任务。

    分析:首先能想到,m个任务最多需要m个人,而一些任务可以由同一个人完成,因此对任务i和j,若i和j能由一人完成,则在i和j之间连一条边,并让m减一。于是可以想到,我们可以将每个任务作为一个节点,若i和j任务能由一人完成,则连一条由i指向j的边,由此构成一个二分图,再求其最大匹配max,最后输出m-max就行了。

              当然,为了判断i任务完成后是否能去完成j任务,由此还需要用floyd先求出任意两点之间的最短路径,若t[i]+d[i]+dis[p[i]][p[j]]<=t[j],才能在i和j之间连边。

代码如下:

#include <cstdio>#include <stack>#include <set>#include <iostream>#include <string>#include <vector>#include <queue>#include <list>#include <functional>#include <cstring>#include <algorithm>#include <cctype>#include <string>#include <map>#include <iomanip>#include <cmath>#define LL long long#define ULL unsigned long long#define SZ(x) (int)x.size()#define Lowbit(x) ((x) & (-x))#define MP(a, b) make_pair(a, b)#define MS(arr, num) memset(arr, num, sizeof(arr))#define PB push_back#define F first#define S second#define ROP freopen("input.txt", "r", stdin);#define MID(a, b) (a + ((b - a) >> 1))#define lson l,mid,rt<<1#define rson mid+1,r,rt<<1|1#define lrt rt << 1#define rrt rt << 1|1#define root 1,m,1#define BitCount(x) __builtin_popcount(x)#define BitCountll(x) __builtin_popcountll(x)#define LeftPos(x) 32 - __builtin_clz(x) - 1#define LeftPosll(x) 64 - __builtin_clzll(x) - 1const double PI = acos(-1.0);const int INF =1000000000;using namespace std;const double eps = 1e-5;const int MAXN = 300 + 10;const int MOD = 1000007;const double M=1e-8;const int N=1100;typedef pair<int, int> pii;typedef pair<int, string> pis;int n,m,g[N][N],d[N][N];int p[N],t[N],s[N];bool vis[N];int link[N];void floyd(){    for (int k=1;k<=n;k++)        for (int i=1;i<=n;i++)            for (int j=1;j<=n;j++) if (d[i][j]>d[i][k]+d[k][j])    {        d[i][j]=d[i][k]+d[k][j];    }}void build()         // 构建二分图{    int i,j;    MS(g,0);    for(i = 1;i <= m;++i)    {        int ed = t[i] + s[i];        for(j = 1;j <= m;++j)        {            if(i == j)  continue;            int a = p[i];            int b = p[j];            if(d[a][b] + ed <= t[j])                g[i][j] = 1;   //  i任务结束后能去做j任务        }    }}bool dfs(int x){    for (int i=1;i<=m;i++) if (g[x][i] && !vis[i]) {        vis[i]=true;        if (link[i]==-1 || dfs(link[i])) {            link[i]=x;            return true;        }    }    return false;}int main(){    int i,j;    while(~scanf("%d%d",&n,&m),n)    {        for (i=1;i<=n;i++)            for (j=1;j<=n;j++)        {            scanf("%d",&d[i][j]);            if (d[i][j]<0) d[i][j]=INF;        }        floyd();    //   用floyd求出任意两点之间的最短路径        for (i=1;i<=m;i++){            scanf("%d%d%d",p+i,t+i,s+i);        }        build();        int cnt=0;        MS(link,-1);  // 匈牙利算法        for (i=1;i<=m;i++) {            MS(vis,false);            if (dfs(i)) cnt++;        }        printf("%d\n",m-cnt);    }}/*4 40 1 2 -11 0 -1 12 -1 0 1-1 1 1 01 1 12 4 13 4 14 10 1*/



0 0
原创粉丝点击