二分图最大匹配例题

        做了一些关于二分图最大匹配的题目,再次进行一些总结。在做题过程中也学到了不少的方法技巧。

        首先要谈一谈关于二分图的构建问题,二分图匹配类问题的求解是简单的,我认为比较难的部分在于如何想到二分图最大匹配,则需要对该类问题的特点有一定的认识,其次,如何构建二分图对原问题进行抽象非常关键。

        这里推荐几个讲解二分图比较详细的博客:

        http://dsqiu.iteye.com/blog/1689505

        http://blog.csdn.net/xuguangsoft/article/details/7861988

        http://www.cnblogs.com/penseur/

        在百度文库里发现了关于二分图建图的一些注意点的文章,写的很不错。掌握一定的二分图建图技巧可以让我们事半功倍。

        该文章叫《二分图最大匹配及常用建图方法》,文中介绍了几种常见的二分图建图方法,第一种行列匹配法,则在下面的例题中也出现了,由于我先看的这篇文章,所以能够较容易的想到用行列匹配法。第二种是黑白染色法,这个也很常见。第三种是反建图法(与集合求补集的思想相同,主要定理支撑是:二分图点集大小=二分图最大独立集大小+二分图最大匹配数)。第四种是拆点法,将一个点拆成两个点或多个点,这是一种假想的拆分,目的是满足二分图的条件。第五种是增行增列法。在题目中比较常见的是前四种。

        下面通过一些例题进行小结。


例题1:HDU 1083&&POJ 1469

Courses

                                 Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
                                                           Total Submission(s): 4034    Accepted Submission(s): 1928


Problem Description
Consider a group of N students and P courses. Each student visits zero, one or more than one courses. Your task is to determine whether it is possible to form a committee of exactly P students that satisfies simultaneously the conditions:

. every student in the committee represents a different course (a student can represent a course if he/she visits that course)

. each course has a representative in the committee

Your program should read sets of data from a text file. The first line of the input file contains the number of the data sets. Each data set is presented in the following format:

P N
Count1 Student1 1 Student1 2 ... Student1 Count1
Count2 Student2 1 Student2 2 ... Student2 Count2
...... 
CountP StudentP 1 StudentP 2 ... StudentP CountP

The first line in each data set contains two positive integers separated by one blank: P (1 <= P <= 100) - the number of courses and N (1 <= N <= 300) - the number of students. The next P lines describe in sequence of the courses . from course 1 to course P, each line describing a course. The description of course i is a line that starts with an integer Count i (0 <= Count i <= N) representing the number of students visiting course i. Next, after a blank, you'll find the Count i students, visiting the course, each two consecutive separated by one blank. Students are numbered with the positive integers from 1 to N.

There are no blank lines between consecutive sets of data. Input data are correct.

The result of the program is on the standard output. For each input data set the program prints on a single line "YES" if it is possible to form a committee and "NO" otherwise. There should not be any leading blanks at the start of the line.

An example of program input and output:
 

Sample Input
  
  
  
2 3 3 3 1 2 3 2 1 2 1 1 3 3 2 1 3 2 1 3 1 1
 

Sample Output
  
  
  
YES NO
 

Source
 

Recommend
We have carefully selected several similar problems for you:   1068  2444  2063  1150  1281 
 
       
        这道题是比较典型的二分图匹配,而且建图也比较好建。这道题直接给出了课程和学生这两个集合,而且这两个集合内部相互独立。学生选课构成一种关系。因此我们在学生与课程这两个集合的对应点之间进行连边操作。边代表的含义是学生选课。在建图的时候一定要清楚图的点集和边集分别代表的含义。这一点很重要。而本题比较常规,因此该重要性体现不明显。还有一点需要注意的是建图的时候要明确自己构建的二分图是有向图还是无向图,因为是否有向将会影响到最终二分图的匹配数。因此需要格外注意。还需要判断能否构建无向图,是否必须构建无向图等。
       这道题由于学生与课程之间的关系存在选与被选的关系,这个关系是相互的。因此是可以构建无向图的。当然我们也可以构建有向图。
       下面是二分图最大匹配的代码:
        
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#define pb push_back
#define CLR(x) memset(x,0,sizeof(x))
#define __CLR(x) memset(x,-1,sizeof(x))
using namespace std;

int a[310],b[310],vis[310],match[310],p,n;
vector<int>G[505];
bool dfs(int u)
{
    for(int i=0; i<G[u].size(); i++)
    {
        int t=G[u][i];
        if(!vis[t])
        {
            vis[t]=1;
            if(match[t]==-1||dfs(match[t]))
            {
                match[t]=u;
                return true;
            }
        }
    }
    return false;
}

int main()
{
    //ios_base::sync_with_stdio(0);
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&p,&n);
        bool flag=1;
        for(int i=1; i<=p; i++)
        {
            int k;
            scanf("%d",&k);
            if(!k) flag=0;
            for(int j=0; j<k; j++)
            {
                int v;
                scanf("%d",&v);
                G[i].pb(v);
                //G[v].pb(i);         //如果把这句话加上就错了。我们建图相当于之保留了二分图中一个集合中的所有点。而只需保留另一个集合中与之相连的点的标号即可。如果用邻接矩阵存储,则邻接矩阵的行列分别代表的是两个集合,所以这样的边是有向的。
           //当然如果想建立无向图,可以在同一个图中存储两个不同的点集。只要保证标号不同即可。
           //如:G[i].pb(p+v); G[p+v].pb(i);
            }
        }
        if(flag)
        {
            int num=0;
            __CLR(match);
            for(int i=1; i<=p; i++)
            {
                CLR(vis);
                if(dfs(i))
                    num++;
            }
            if(num==p) printf("YES\n");
            else printf("NO\n");
        }
        else
            printf("NO\n");
        for(int i=0; i<505; i++)
            G[i].clear();
    }
}


例题2 POJ 3020(最小路径覆盖)

Antenna Placement
Time Limit: 1000MS   Memory Limit: 65536K
Total Submissions: 6966   Accepted: 3457

Description

The Global Aerial Research Centre has been allotted the task of building the fifth generation of mobile phone nets in Sweden. The most striking reason why they got the job, is their discovery of a new, highly noise resistant, antenna. It is called 4DAir, and comes in four types. Each type can only transmit and receive signals in a direction aligned with a (slightly skewed) latitudinal and longitudinal grid, because of the interacting electromagnetic field of the earth. The four types correspond to antennas operating in the directions north, west, south, and east, respectively. Below is an example picture of places of interest, depicted by twelve small rings, and nine 4DAir antennas depicted by ellipses covering them. 
 
Obviously, it is desirable to use as few antennas as possible, but still provide coverage for each place of interest. We model the problem as follows: Let A be a rectangular matrix describing the surface of Sweden, where an entry of A either is a point of interest, which must be covered by at least one antenna, or empty space. Antennas can only be positioned at an entry in A. When an antenna is placed at row r and column c, this entry is considered covered, but also one of the neighbouring entries (c+1,r),(c,r+1),(c-1,r), or (c,r-1), is covered depending on the type chosen for this particular antenna. What is the least number of antennas for which there exists a placement in A such that all points of interest are covered? 

Input

On the first row of input is a single positive integer n, specifying the number of scenarios that follow. Each scenario begins with a row containing two positive integers h and w, with 1 <= h <= 40 and 0 < w <= 10. Thereafter is a matrix presented, describing the points of interest in Sweden in the form of h lines, each containing w characters from the set ['*','o']. A '*'-character symbolises a point of interest, whereas a 'o'-character represents open space. 

Output

For each scenario, output the minimum number of antennas necessary to cover all '*'-entries in the scenario's matrix, on a row of its own.

Sample Input

2
7 9
ooo**oooo
**oo*ooo*
o*oo**o**
ooooooooo
*******oo
o*o*oo*oo
*******oo
10 1
*
*
*
o
*
*
*
*
*
*

Sample Output

17
5

Source


        这道题是一道比较经典的最小路径覆盖问题。在说这道题之前,我们首先需要了解最小路径覆盖与最小顶点覆盖之间的区别。这些细微的差别必须搞清楚,否则会导致完全偏离题意。最小路径覆盖是用最少的边来覆盖所有的点,而最小顶点覆盖是用最少的顶点来保证所有的边都与这些顶点相连,即用最少的顶点来覆盖所有的边。这是两个完全不同的方向。而本题中,设立一个基站后,则这个基站可以与它周围相邻的四个点的任意一个点形成一个覆盖区域。而这种覆盖的关系就相当于是在这两个点之间进行连边。而我们需要关注的是要覆盖所有的‘*’。因此只需要考虑‘*’符号之间的关系即可。即该集合内所有点之间的关系,是否具有覆盖关系。而我们最终目的是用边来覆盖所有点,即用最少的边覆盖所有的点。这就顺理成章的与最小路径覆盖问题吻合。其中比较难想的是把这种覆盖四周相邻一点的特点理解为边。从而在‘*’点之间连边。

       而本题最有疑问的是为何它是二分图最大匹配?首先我们需要清楚这个图是不是二分图,回答是。而本题使用的二分图建图方法就是拆点法。比如i点与j点间有一条边,而实质上是i与j拆出来的一个j'点相连。这样就出现了两个集合。而这实际上是一种构想。这一点也是非常巧妙的地方。把同一个集合拆分出两个集合进行二分图的最大匹配。最终由定理:最小路径覆盖数=顶点数-最大匹配数(如果是无向图,要除以2)。

      参考代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#define pb push_back
#define CLR(x) memset(x,0,sizeof(x))
#define __CLR(x) memset(x,-1,sizeof(x))
using namespace std;

char a[50][20];
int b[50][20];
bool vis[500];
vector<int> G[500];
int match[500];
int dx[]={0,0,-1,1};
int dy[]={1,-1,0,0};
int n,m;

bool dfs(int u)
{
    for(int i=0;i<G[u].size();i++)
    {
        int t=G[u][i];
        if(!vis[t])
        {
            vis[t]=1;
            if(match[t]==-1||dfs(match[t]))
            {
                match[t]=u;
                return true;
            }
        }
    }
    return false;
}

int main()
{
    
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1; i<=n; i++)
            scanf("%s",a[i]+1);
        CLR(b);
        int num=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                if(a[i][j]=='*')
                    b[i][j]=++num;
            }
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                if(a[i][j]=='*')
                {
                    for(int k=0;k<4;k++)
                    {
                        int x1=i+dx[k],y1=j+dy[k];
                        if(x1>=1&&x1<=n&&y1>=1&&y1<=m)
                        {
                            if(a[x1][y1]=='*')
                            {
                                G[b[i][j]].pb(b[x1][y1]);
                                G[b[x1][y1]].pb(b[i][j]);
                            }
                        }
                    }
                }
            }
        }
        __CLR(match);
        int ans=0;
        for(int i=1;i<=num;i++
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值