给定一个二分图G(无向图),在G的一个子图M中,M的边集中的任意两条边都不依附于同一个顶点,则称M是一个匹配.
选择这样的边数最大的子集称为图的最大匹配问题(maximal matchingproblem)
如果一个匹配中,图中的每个顶点都和图中某条边相关联,则称此匹配为完全匹配,也称作完备匹配。
如果该二分图的每条边都有一个权值且存在完备匹配,那么我们要找出一个所有边权值和最大的完备匹配的问题叫做二分图的最优匹配问题。
(在下面给出了求二分图最大匹配和最优匹配的模板代码)
二分图的最小覆盖数:在二分图中选取最少数目的点集,使得二分图任意一边都至少有一个端点在该点集中。这个点集的大小是二分图的最小覆盖数,且二分图的最小覆盖数==二分图的最大匹配数。
二分图的最大独立集:在二分图中选取最多数目的点集,使得该点集中的任意两点在二分图中都不存在一条边相连。这个点集就是二分图的最大独立集。且二分图的最大独立集大小==|G|(二分图顶点数) - 二分图最大匹配数。
下面是求二分图最大匹配的模板
//二分图最大匹配模板,二分图都是无向图
//调用下面算法前,保证本图是二分图
/*************vecotr模板*****************/
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=100+5;
struct Max_Match
{
int n,m;//左右点集大小,点从1开始编号
vector<int> g[maxn];//g[i]表示左边第i个点邻接的右边点的集合
bool vis[maxn];//vis[i]表示右边第i个点是否在本次match中被访问过
int left[maxn];//left[i]==j表右边第i个点与左边第j个点匹配,为-1表无点匹配
void init(int n,int m)
{
this->n=n;
this->m=m;
for(int i=1;i<=n;i++) g[i].clear();
memset(left,-1,sizeof(left));
}
//判断从左u点是否可以找到一条增广路
bool match(int u)
{
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(!vis[v])
{
vis[v]=true;
if(left[v]==-1 || match(left[v]))//找到增广路
{
left[v]=u;
return true;
}
}
}
return false;
}
//返回当前二分图的最大匹配数
int solve()
{
int ans=0;//最大匹配数
for(int i=1;i<=n;i++)//每个左边的节点找一次增广路
{
memset(vis,0,sizeof(vis));
if(match(i)) ans++;//找到一条增广路,形成一个新匹配
}
return ans;
}
}MM;
/*************vecotr模板*****************/
例题。。。。。。
题意:
有P门课和N个学生,每门课可能有0个或多个学生选修想选.现在问你能不能找到一种选课方案,使得P门课每门课都正好只有1个学生选修,且任意两个选了课的学生所选的课都不同?
分析:
注意:二分图是无向图,但是二分图中的g数组只保存从左边点集到右边点集的边.即g[i]==j,只代表左边i点与右边j点连接了一条无向边,不代表右边i点也与左边j点连了边. 以上结论一定要牢记.
因为只有课与学生之间存在边,所以该图可以看成是左边有P个点,右边有N个点的二分图.
又由于我们想使得课与学生连的边最多且每个学生只能连一门课,每门课也只能有一个学生连接(即每个学生或每门课最多只有一条边依附).所以这就是一个匹配问题且是最大匹配.
现在要求这个图的最大匹配,看看能否该最大匹配边数目==P.
poj1469--->请点这里
代码:
#include<cstdio>
#include<cstring>
using namespace std;
struct Max_Match
{
int p,n;//左右两点集的点个数
bool g[110][310];//邻接矩阵,g[i][j]=true表示从左边第i个节点到右边第j个节点有边
int left[310];//left[i]==j表右边第i个点与左边第j个点匹配,为-1表无点匹配
bool vis[310];//表右边第i个点是否已经被访问过
void init()
{
memset(g,0,sizeof(g));
memset(left,-1,sizeof(left));
}
bool match(int i)
{
for(int j=1;j<=n;j++)if(g[i][j] && !vis[j])
{
vis[j]=true;
if(left[j]==-1 || match(left[j]) )
{
left[j]=i;
return true;
}
}
return false;
}
int solve()
{
int ans=0;//记录匹配边数目
for(int i=1;i<=p;i++)
{
memset(vis,0,sizeof(vis));
if(match(i)) ans++;
}
return ans;
}
}MM;
int main()
{
int T; scanf("%d",&T);
while(T--)
{
MM.init();
bool ok=true;
scanf("%d%d",&MM.p,&MM.n);
for(int i=1;i<=MM.p;i++)
{
int num;
scanf("%d",&num);
if(num)
{
while(num--)
{
int j;
scanf("%d",&j);
MM.g[i][j]=true;
}
}
else ok=false;
}
if(ok) printf("%s\n",MM.solve()==MM.p?"YES":"NO");
else printf("NO\n");
}
return 0;
}