做了一些关于二分图最大匹配的题目,再次进行一些总结。在做题过程中也学到了不少的方法技巧。
首先要谈一谈关于二分图的构建问题,二分图匹配类问题的求解是简单的,我认为比较难的部分在于如何想到二分图最大匹配,则需要对该类问题的特点有一定的认识,其次,如何构建二分图对原问题进行抽象非常关键。
这里推荐几个讲解二分图比较详细的博客:
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
. 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:
2 3 3 3 1 2 3 2 1 2 1 1 3 3 2 1 3 2 1 3 1 1
YES NO
#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(最小路径覆盖)
Time Limit: 1000MS | Memory Limit: 65536K | |
Total Submissions: 6966 | Accepted: 3457 |
Description
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
Output
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++