二分图匹配(一)
文章目录
什么是二分图:
二分图充分必要条件条件:
至少有两个顶点且没有奇环
二分图判断:
通过黑白染色
例题:
NC111768 CF741C
题目描述:
有n对情侣(2n个人)围成一圈坐在桌子边上,每个人占据一个位子,要求情侣不能吃同一
种食物,并且桌子上相邻的三个人的食物必须有两个人是不同的,只有两种食物(1或者是2),问一种可行分配方式。
题解:
如果我们能把不能吃同一种食物的人连边,问题就变成二分图黑白染色
• 所以情侣关系等价于两者之间连一条边
• “每连续的三个人不能都一样”怎么办?
• 让第2i个人和第2i+1个人不能吃一样的食物即可(即1连2,3连4,5连6以此类推)
• 这样肯定是个二分图——2i和2i-1分别连了他两的情侣,情侣又分别连他两的一个邻居……
每次都是给这个可能存在的环加两个点,所以有环就一定不是奇环
构造方法如下:首先情侣连边,然后在原图的相邻点对上,隔一对连一条边
代码:
二分图最大匹配
促成更多的点配对
匈牙利算法
有一场宴会,男孩和女孩组成舞伴,并且他们必须互相喜欢才能成为舞伴,一个男孩可能喜欢0个或多个女孩,一个女孩也可能喜欢0个或多个男孩,但一个男孩和他喜欢地女孩成为舞伴之后就不能和其他他喜欢地女孩成为舞伴,女孩亦是如此。请问最多可以有多少对舞伴。
算法思想:
对于一个男孩子x,如果他喜欢女孩y,且y还没有舞伴——让他们配对
• 如果y有了舞伴,x还是会去尝试邀请y,如果y发现她的舞伴z可以换一个舞伴,y就主动抛弃掉z(在确定z可以和别人牵手之后),和x成为舞伴
增广路(交错路):
路径的起点和终点都是还没有匹配过的点,并且路径经过的连线是一条没被匹配、一条已经匹配过,再下一条又没匹配这样交替地出现。找到这样的路径后,显然路径里没被匹配的连线比已经匹配了的连线多一条,于是修改匹配图,把路径里所有匹配过的连线去掉匹配关系,把没有匹配的连线变成匹配的,这样匹配数就比原来多1个。
代码:
时间复杂度:
邻接矩阵O(n^3)
邻接表:O(n*m)
bool dfs(int x)
{
for(int i=1;i<=n;i++)//遍历女生
{
if(!a[x][i]||vis[i])continue;//没连线或者已被找过
vis[i]=1;
if(link[i]==0||dfs(link[i]))//link[i]表示第i个女生的男伴是谁
{
link[i]=x;
return 1;
}
}
return 0;
}
int main()
{
for(int i=1;i<=n;i++)//遍历男生
{
memset(vis,0,sizeof(vis));//vis[i]第i个女生在这次找搭档的过程中被邀请
sum+=dfs(i);
}
}
König定理
二分图中最大匹配数等于这个图中的最小点覆盖数
最小点覆盖:
每个点覆盖以它为端点的所有边,选择最少的点来覆盖所有的边
(选最少的人,联系到其他的所有的人)
证明:
视频时间:01:03:42
二分图最优匹配
存在边权,问最大匹配下的最大边权
KM(Kuhn-Munkres)算法
最优匹配是建立在完美匹配的基础上的,如果不存在完美匹配,那么本算法失效(但是,我们可以人为连一些权值为0的边,甚至加点,使得没有匹配的节点们最后都有一个“虚假”的匹配)。
算法思路:
最开始将每个左边节点连的权值最大的边视为有效边,在匹配给过程当中如果某个点找不到匹配点,则选择将一些无效边改成有效边继续去匹配,选择的这些无效边其实是换掉的原来的某条有效边,那么我们肯定选换掉的代价最小的。
先让所有人选最佳搭档,当发生冲突时,让降低标准最少的人来换搭档
具体操作
给每个点预设一个顶标,只有两个端点的顶标加起来等于边权的边,我们才认为是有效边,
即L[x]+R[y]==w[x][y]的时候才是有效边
• 最开始左集合的每个点的顶标为他连出去权值最大的边的权值,右集合每个点顶标为0,当匹配失败的时候,我们遍历之前的左集合本次尝试匹配遍历过的点,在他们的连向右集合未遍历的点的边中找一个与顶标和差值最小的边,即delta=w[x][y]-L[x]-R[y]最小的边(可理解为找出一条损失最小的找出一条增广路。)找到之后修改顶标:左侧所有遍历过的点减去
delta,右边所有遍历过的点加上delta,这样原来的有效边还是有效边,而我们新加的边也已经加上了。
• 重复找匹配+修改顶标的操作,一直到找到一个完美匹配即可
详细过程:
降低的标准x,左边-x,右边+x
每个人都要为了团队牺牲自己,甘愿降低标准
代码:
lx[]//左端点,赋值为0x7f
ly[]//右端点,赋值为0
link[i]=j//第i个人匹配的是j
int dfs(int x)
{
visx[x]=1;
for(int i=1;i<=m;i++)
{
if(!visy[i]&&lx[x]+ly[i]==w[x][i])//有边且为有效边
{
visy[i]=1;
if(link[i]==-1||dfs(link[i]))
{
link[i]=t;
return 1;
}
}
}
return 0;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
lx[i]=max(w[i][j],lx[i]);//最心动的边
}
}
for(int i=1;i<=n;i++)
{
while(1)//全部找到完匹配才能结束
{
memset(visx,0,sizeof(visx));
memset(visy,0,sizeof(visy));
if(dfs(i))break;//已经给第i人找到匹配
int d=0x7f7f7f7f;
for(int j=1;j<=n;j++)
{
if(visx[j])//要降低标准的男生
{
for(int k=1;k<=m;k++)
{
if(!visy[k])//引入新的女生
d=min(d,lx[j]+ly[k]-w[j][k]);//求最小的代价
}
}
}
for(d==0x7f7f7f7f)return -1;//匹配失败
for(int j=1;j<=n;j++)if(visx[j])lx[j]-=d;
for(int j=1;j<=m;j++)if(visy[j])ly[j]+=d;
}
}
//n<=m
//保证所有男生都匹配(左点),女生不一定
复杂度
·