算法{二分图的最小点覆盖集,二分图的最大点独立集}
@LOC_2
二分图的最小点覆盖集
定义
点覆盖集的特例, 即当图为二分图时;
给定二分图, 从中选择若干个点S,满足对于二分图中的任意边l->r
一定有l \in S || r \in S
;
.
比如二分图是l0->r0, l0->r1
, 则S可以为{l0}, {r0,r1}, {l0,r0}, ...
;
#结论#: |S|
的最小值 等于 二分图的最大匹配;
.
证明: @LINK: @LOC_0
;
#结论#: 构造S
.
证明: @LINK: @LOC_1
;
性质
最小点覆盖集,他是最小权的点覆盖集的特例,即当所有点权值均为1
时 此时最小权的点覆盖集 就等于 最小点覆盖集;
@DELI;
点覆盖集的补集 一定是点独立集; (证明参见点覆盖集);
@DELI;
@MARK: @LOC_1
;
构造S
;
现在还没弄懂, 暂时参考oi-wiki
的讲解;
将二分图点集分成左右两个集合,使得所有边的两个端点都不在一个集合。
考虑如下构造:从左侧未匹配的节点出发,按照匈牙利算法中增广路的方式走,即先走一条未匹配边,再走一条匹配边。由于已经求出了最大匹配,所以这样的「增广路」一定以匹配边结束,即增广路是不完整的。在所有经过这样「增广路」的节点上打标记。则最后构造的集合是:所有左侧未打标记的节点和所有右侧打了标记的节点。
首先,这个集合的大小等于最大匹配。左边未打标记的点都一定对应着一个匹配边(否则会以这个点为起点开始标记),右边打了标记的节点一定在一条不完整的增广路上,也会对应一个匹配边。假设存在一条匹配边左侧标记了,右侧没标记,左边的点只能是通过另一条匹配边走过来,此时左边的点有两条匹配边,不符合最大匹配的规定;假设存在一条匹配边左侧没标记,右侧标记了,那就会从右边的点沿着这条匹配边走过来,从而左侧也有标记。因此,每一条匹配的边两侧一定都有标记(在不完整的增广路上)或都没有标记,匹配边的两个节点中必然有一个被选中。
其次,这个集合是一个点覆盖。由于我们的构造方式是:所有左侧未打标记的节点和所有右侧打了标记的节点。假设存在左侧打标记且右侧没打标记的边,对于匹配边,上一段已经说明其不存在,对于非匹配边,右端点一定会由这条非匹配边经过,从而被打上标记。因此,这样的构造能够覆盖所有边。
同时,不存在更小的点覆盖。为了覆盖最大匹配的所有边,至少要有最大匹配边数的点数。
@DELI;
@MARK: @LOC_0
;
证明|S| = 最大匹配
;
从流网络的角度, 在求完最大流后(最大流等于最大匹配) 你只要证明: 这个最大流P 他可以等效的转换为另一个流T(流量不变) 且T在所有的边上 都有流量 (之前的最大流 他每条边都是整数流量, 现在T流 他会涉及到小数流量);
.
P流 其实他就对应了 若干条互不相交的路径 每个路径就是一个匹配, 比如S->l0->r0->T, S->l1->r1->T
每个路径的流量都是1 总路径个数等于总流量等于最大匹配;
对于任意一条边l->r
, 我们要使得 他必须有流量经过, 有如下情况:
@IF(这条边在P中有流量): 已经满足条件了;
@ELSE(l->r
这条边无流量):
.
@IF(l,r在P流里
有流量): 即S->l->R0->T, S->L0->r->T
, 此时取一个极小值的小数D=1e-100
, 从第一个路径里 抽取D
流量 让他流到r
里 即S->l->R0->T
让一些流量变成S->l->r->T
; 但这会导致r->T
的流量溢出了D
因此再从第二个路径里 同样抽取D流量 让他流到l
里 即S->L0->r->T
的一些流量 变成S->l->r->T
; 此时 这条边就有流量了;
.
@ELIF(l在P流里
有流量 但r
没有): S->l->R0->T
里抽取一些 改成S->l->r->T
;
.
@ELIF(r在P流里
有流量 但l
没有): S->L0->r->T
里抽取一些 改成S->l->r->T
;
.
@ELSE: 这是不可能的, 否则你就得到了更大的流;
@DELI;
算法
构造最小点覆盖
//{ ___BipartiteGraph_MinimalPointsCoverage_ (二分图的最小点覆盖,构造方案)
template< class _TG_> void ___BipartiteGraph_MinimalPointsCoverage_( _TG_ const& _g){
int N = _g.__PointsCount;
___Graph_<int> G_flow;
G_flow.Initialize( N+2, (_g.__EdgesCount + N) * 2, 1);
int src = N, tar = src + 1;
for( int a = 0; a < N; ++a){
if( _g.FirstEdge[a] == -1){ // `a`是二分图的右集合里的;
G_flow.AddEdge_Undirected( a, tar, {1,0});
continue;
}
G_flow.AddEdge_Undirected( src, a, {1,0});
for( auto & nex : _g[a]){
G_flow.AddEdge_Undirected( a, nex.EndPoint, {1,0});
}
}
int const MaxMatch = ___NetworkFlow_MaxFlow( src, tar, G_flow);
std::vector<int> Match( N, -1); // 任意匹配`l-r` 一定有`Match[r]=l, Match[l]=r`;
for( int l = 0; l < N; ++l){
if( _g.FirstEdge[l] == -1){ continue;}
for( auto it = G_flow[l].begin(); it != G_flow[l].end(); ++it){
if( it.CurEdgeID & 1){ continue;}
if( it->EndPoint >= N){ continue;}
if( it->Weight != 0){ continue;}
Match[ it->EndPoint] = l; Match[ l] = it->EndPoint;
}
}
std::vector<int> ANS; // 最小点覆盖
std::vector<char> Vis( _g.__PointsCount, 0);
auto Dfs = [&]( auto _dfs, int _r)->void{
Vis[ _r] = 1;
auto l = Match[ _r];
Vis[ l] = 1;
for( auto & [r,w] : _g[l]){
if( Vis[r]){ continue;}
_dfs( _dfs, r);
}
};
for( int l = 0; l < _g.__PointsCount; ++l){
if( _g.FirstEdge[ l] == -1){ continue;}
if( Match[l] != -1){ continue;}
Vis[ l] = 1;
for( auto const& nex : _g[l]){
if( Vis[nex.EndPoint] == 0){ Dfs( Dfs, nex.EndPoint);}
}
}
for( int a = 0; a < _g.__PointsCount; ++a){
if( _g.FirstEdge[ a] == -1){ // `a`是右集合里的;
if( Match[a] == -1){ continue;}
if( Vis[a] == 0){ continue;}
}
else{
if( Match[a] == -1){ continue;}
if( Vis[a]){ continue;}
}
ANS.emplace_back( a);
}
ASSERTsystem_( (int)ANS.size() == MaxMatch);
}
//} ___BipartiteGraph_MinimalPointsCoverage_
例题
@LINK: https://editor.csdn.net/md/?articleId=128548772
;
模板;
二分图的最大点独立集
定义
点独立集的特例, 即当图为二分图时;
给定二分图, 从中选择若干个点S,满足对于二分图中的任意边l->r
一定有!(l \in S && r \in S)
;
.
比如二分图是l0->r0, l0->r1
, 则S可以为{r0,r1}, {l0}, ...
;
因此, 一切求最大独立集的问题, 都可以转换为: 求最小点覆盖; 只要求出点覆盖 取个补集 就得到了独立集;
性质
独立集的补集 一定是点覆盖; (证明参见点独立集);
例题
@LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=128571164
;
@TODO
对于独立集, 再建立一个图G, 如果原二分图中 a,b
之间没有边 则连接G: [a-b]
, 那么G拆点后的二分图 的最大匹配 就是等于 原图的独立集把?