图论补档——KM算法+稳定婚姻问题
突然发现考前复习图论的时候直接把 KM 和 稳定婚姻 给跳了……emmm
结果现在刷训练指南就疯狂补档。QAQ。
KM算法——二分图最大带权匹配
提出问题
(不严谨定义,理解即可)
二分图 定义:将点集 \(V\) 划分成两个不相交的集合 \(V_1,V_2\) (通常称为左右部点)使得不存在 \(u\in V_1,v\in V_2\) 且 \((u,v)\in E\) .
最大匹配 :给定一张二分图,求一个子图 \(G'\) ,称 \(G'\) 中的边为匹配边,原图 \(G\) 中的其他边为非匹配边,限制 \(G'\) 中每个点度数不超过 1,求匹配边的最大数量。
前置知识:匈牙利算法解决二分图最大匹配问题
定义 交替路 为:从一个未匹配点出发,依次经过非匹配边、匹配边交替形成的一条路径
定义 增广路 为:从一个未匹配点出发,以一个未匹配点结束的交替路。
很显然,当我们在原图中找出一条增广路时,非匹配边恰好比匹配边多一条,那么只要对这条路上匹配和非匹配情况取反,就可以使得匹配边数目增加1.重复这个过程,直到找不到增广路,得到的匹配就是最大匹配。
剩余流程和代码见 这里 (该链接已经指向匈牙利算法部分)
完备匹配 :左右部点个数均为 \(n\) 且最大匹配包含 \(n\) 条边。
带权匹配 :二分图中每条边有权值,在最大匹配的基础 上最大化这个边集的 权值和 。
分析问题
解决带权匹配一般有两种思路:
- 费用流
- KM算法(只适用于存在完备匹配的情况,在稠密图上效率较高。)
这里只讨论 KM 算法。
定义
顶标 :顾名思义,顶点的标记,存在于左右部点。为了方便叙述,左部点的顶标称为 \(a_i\) ,右部点的顶标称为 \(b_i\) ,顶标不唯一,可以调整。令左部点 \(i\) 和右部点 \(j\) 之间的边权为 \(w_{i,j}\) ,那么两个顶标满足 \(a_i+b_j=w_{i,j}\) .
相等边 : 满足 \(a_i+b_j=w_{i,j}\) 的边 \((i,j)\)
相等子图 :相等边构成的子图
交错树 :增广路径形成的树。考虑什么时候会匹配失败。发现我们当前求相等子图的完备匹配失败了,是因为对于某个左部点,我们找不到一条从它出发的增广路。那么这时候所有增广路形成了交错树,而且由于无法增广,叶子节点一定都是左部点。
结论
一个显然的结论:当每个相等子图完备匹配时,二分图得到最大匹配
流程
根据上面的定义,现在我们需要找到一个合适的顶标分配使得 “每个相等子图完备匹配”
具体步骤:
-
分配可行顶标。为了让 \(a_i+b_j\ge w_{i,j}\) 恒成立,令 \(a_i\) 为左部点 \(x_i\) 所有连边中的最大边权,\(b_j=0\) .
-
匈牙利算法找增广路。这部分就不在赘述。
-
找不到增广路就调整顶标。为了让更多的点进入相等子图,我们把交错树中左部点的顶标都减小某个值 \(del\) ,右部点的顶标都增加 \(del\) ,那么会发现:
- 对于原本就在交错树中的边 \((i,j)\) ,\(a_i+b_j\) 不变,仍然在相等子图中。
- 对于两端都不在交错树上的边 \((i,j)\) ,\(a_i,b_j\) 不变,仍然不在相等子图中。
- 左端在交错树中,右端不在的边 \((i,j)\) ,\(a_i+b_j\) 减小,仍然不在相等子图中。
- 左端不在交错树中,右端在的边 \((i,j)\) ,\(a_i+b_j\) 增大,有可能进入相等子图
这样相等子图就得到了扩大。
-
重复 2、3 直到找到增广路
现在的问题就是如何求这个 \(del\) 了。为了让 \(a_i+b_j\ge w_{i,j}\) 始终成立,且至少有一条边进入相等子图,那么 \(del=\min\{a_i+b_j-s_{i,j}\}\) .
解决问题
优良模板题 花姐姐的数据用心了awa
DFS
一次只能找一条增广路,复杂度可以卡成 \(\mathcal{O}(n^4)\) .
据说这个东西能过 50 分……但是不知道是我常数大还是写假了,反正我只有 10pts,其余全 TLE。
这不重要,反正 BFS 的复杂度才是对的
//Author: RingweEH
const int N=510;
const ll inf=0x7f7f7f7f;
int n,m,match[N],visa[N],visb[N];
ll edge[N][N],del[N],vala[N],valb[N];
bool dfs( int u )
{
visa[u]=1;
for ( int v=1; v<=n; v++ )
if ( !visb[v] )
{
if ( vala[u]+valb[v]-edge[u][v]==0 )
{
visb[v]=1;
if ( !match[v] || dfs( match[v]) ) return match[v]=u,1;
else del[v]=min( del[v],vala[u]+valb[v]-edge[u][v] );
}
}
return 0;
}
int KM()
{
memset( vala,-inf,sizeof(vala) );
for ( int u=1; u<=n; u++ )
for ( int v=1; v<=n; v++ )
vala[u]=max( vala[u],edge[u][v] );
for ( int u=1; u<=n; u++ )
{
while ( 1 )
{
memset( visa,0,sizeof(visa) ); memset( visb,0,sizeof(visb) );
memset( del,inf,sizeof(del) );
if ( dfs(u) ) break;
ll delta=inf;
for ( int v=1; v<=n; v++ )
if ( !visb[v] ) delta=min( delta,del[v] );
for ( int v=1; v<=n; v++ )
if ( visa[v] ) vala[v]-=delta;
for ( int v=1; v<=n; v++ )
if ( visb[v] ) valb[v]+=delta;
}
}
ll res=0;
for ( int v=1; v<=n; v++ )
res+=edge[match[v]][v];
return res;
}
BFS
换成 BFS 之后复杂度是 \(\mathcal{O}(n^3)\) .(因为 DFS 每次都是从头去找增广路……肯定慢啊)
//Author: RingweEH
const int N=510;
const ll inf=0x7f7f7f7f;
int n,m,match[N],visa[N],visb[N],pas[N];
ll edge[N][N],del[N],vala[N],valb[N],c[N];
void bfs( int u )
{
int a,v=0,vl=0,delta;
for ( int i=1; i<=n; i++ )
pas[i]=0,c[i]=inf;
match[v]=u;
do
{
a=match[v]; delta=inf; visb[v]=1;
for ( int b=1; b<=n; b++ )
if ( !visb[b] )
{
if ( c[b]>vala[a]+valb[b]-edge[a][b] )
c[b]=vala[a]+valb[b]-edge[a][b],pas[b]=v;
if ( c[b]<delta ) delta=c[b],vl=b;
}
for ( int b=0; b<=n; b++ )
if ( visb[b] ) vala[match[b]]-=delta,valb[b]+=delta;
else c[b]-=delta;
v=vl;
}while ( match[v] );
while ( v ) match[v]=match[pas[v]],v=pas[v];
}
ll KM()
{
for ( int i=1; i<=n; i++ )
match[i]=vala[i]=valb[i]=0;
for ( int u=1; u<=n; u++ )
{
for ( int v=1; v<=n; v++ )
visb[v]=0;
bfs( u );
}
ll res=0;
for ( int u=1; u<=n; u++ )
res+=edge[match[u]][u];
return res;
}
What's More?
- 题目可能不存在完备匹配。
- 这种情况可以在 KM 的基础上在右部点里面加虚点使得可以形成完备匹配,然后再加虚边。显然,如果被迫选了虚边,那在原图的情况上就是无解,所以把虚边的权值赋成 \(-inf\) 即可(这是针对无解的情况,如果是非完备匹配那就设成 0 )。
参考
George1123的题解 话说 George 的找遍全网真的不是看的百度百科或者wiki吗
稳定婚姻问题
提出问题
给一张完全二分图,每一对左边的点与右边的点之间都有一个评分 \(w_{i,j}\) ,要求把点两两匹配,满足:
设点 \(a,c\) 在同一边,点 \(b,d\) 在另一边,当前 \((a,b),(c,d)\) 匹配。那么对于所有这种 \((a,b,c,d)\) 不能存在 \(w_{b,a}<w_{b,c}\) ( \(b\) 认为 \(c\) 比 \(a\) 优)且 \(w_{c,b}>w_{c,d}\) ( \(c\) 认为 \(b\) 比 \(d\) 优)的情况。
如果存在,那么显然 \(b,c\) 会组成一对,因为这对于 \(b,c\) 来说都更优,那么他们会各自拒绝自己原来的匹配,就不稳定了。
分析问题
流程
定义 \(match[i]\) 表示点 \(i\) 所匹配的点。
每次取出一个没有匹配的左部点 \(u\) ,
- 如果 \(u\) 的最优匹配 \(v\) 没有匹配,那么 \(u,v\) 匹配
- 如果 \(v\) 有匹配,且对于点 \(v\) ,\(u\) 比它当前的匹配点更优,那么 \(u,v\) 匹配,\(v\) 原先的匹配点失配
直到每个点都有匹配位置。
正确性证明
-
可行性:假设结束后,有一个左部点和一个右部点没有匹配。
- 如果一个左部点一直没有匹配,显然会尝试和所有右部点匹配;
- 如果一个右部点被左部点尝试过了,那么一定会有匹配;
所以右部点一定有匹配,不会出现失配的情况。
-
复杂度:每个左部点最多尝试 \(n\) 次与右部点匹配,上界为 \(\mathcal{O}(n^2)\) .
性质
- 主动方可以选择到他可以匹配到的最优的匹配。
所以是对男生有优势是吗(
解决问题
模板题链接 (洛谷那个长得像模板题的东西是假的,那个是 Tarjan 求 SCC)
//Author: RingweEH
//稳定婚姻问题模板,POJ3487
const int N=40;
int lef[N],list_lr[N][N],list_rl[N][N],nxt_l[N];
int match_l[N],match_r[N],n;
queue<int> q;
void get_match( int le,int ri )
{
int now=match_r[ri];
if ( now )
{
match_l[now]=0; q.push( now );
}
match_r[ri]=le; match_l[le]=ri;
}
int main()
{
ios::sync_with_stdio(0);
int T=read();
while ( T-- )
{
memset( lef,0,sizeof(lef) );
cin>>n; char ch;
for ( int i=0; i<n; i++ )
{
cin>>ch; lef[ch-'a'+1]=1;
}
for ( int i=0; i<n; i++ )
cin>>ch;
char s[N];
for ( int i=0; i<n; i++ )
{
cin>>s; int c=s[0]-'a'+1;
for ( int j=2; s[j]; j++ )
list_lr[c][j-1]=s[j]-'A'+1;
nxt_l[c]=1; match_l[c]=0;
q.push( c );
}
for ( int i=0; i<n; i++ )
{
cin>>s; int c=s[0]-'A'+1;
for ( int j=2; s[j]; j++ )
list_rl[c][s[j]-'a'+1]=j-1;
match_r[c]=0;
}
//-------------Input Finished.--------------
while ( !q.empty() )
{
int now=q.front(); q.pop();
int rnow=list_lr[now][nxt_l[now]++];
if ( !match_r[rnow] ) get_match( now,rnow );
else if ( list_rl[rnow][now]<list_rl[rnow][match_r[rnow]] ) get_match( now,rnow );
else q.push( now );
}
//-------------Matched---------------
for ( int i=1; i<35; i++ )
if ( lef[i] ) cout<<(char)(i-1+'a')<<' '<<(char)(match_l[i]+'A'-1)<<endl;
if ( T ) cout<<endl;
}
return 0;
}