参考博客:
https://blog.csdn.net/xuezhongfenfei/article/details/10148445
https://www.cnblogs.com/zhoushuyu/p/8717234.html
https://www.cnblogs.com/owenyu/p/6858508.html
经典例题:
https://www.cnblogs.com/BAJimH/p/10569418.html
增广路取反:二分图匹配中增广路的顺序为非匹配边->-匹配边>非匹配边->匹配边.>...->非匹配边。取反则为将非匹配边变为匹配边,匹配边变为非匹配边。
自己理解:核心算法还是找增广路,但是由于一般图中有可能存在奇环,那样我们找增广路时,可能会涉及一条边两次。这样就不能通过增广路取反,从而增加匹配的数量。注意到如果一个奇环中有k(k>=3)个点,那么,若内部点匹配的话,就会多出一个点无法匹配,怎么办呢?找环外的点匹配。那么对于一个奇环,他只有一个点没有匹配到,我们就把奇环缩成一个点(俗称开花)。怎么缩点呢?,这里我们用并查集维护。但是,由于无法确定用奇环中的哪个点去找环外点才能得到最大匹配,要把环中的点枚举一遍。而且有可能出现环中有环(花中有花)的情况,我们每次找到增广路后,还需要把环一层层展开。
具体的流程为(粘的上面大佬博客里的):
我们给所有点黑白染色。假设开始增广的点是黑点。把所有黑点压进队列中顺次处理。对于一个黑点u,找与他相邻的点v,会出现一下四种情况:
1、u,v已经被缩成一个点了(这两个点在一朵花里),跳过。
2、v是白点,说明已经被匹配了,也就是偶环,跳过。
3、v还没有被染色。那就先把这个点染成白的,然后尝试与u匹配。如果v还没有匹配就匹配上,增广成功,然后沿增广路取反。如果v已经被匹配了,那么匹配他的点就是个黑点,染色,然后压进队列。
4、v也是黑点。这时候染色发生了冲突,说明遇见了奇环。这时候就需要找到两个点在带花树中的lca,然后把这整个环缩成一个点。(直接开花。)
缩点(开花)过程:
1、找x和y的LCA(的根)L。找LCA可以用各种方法。。。直接朴素也行。
2、在pre数组中把x和y接起来(表示它们形成环了!)
3、从x、y分别走到L,维护并查集使得变成一棵树,同时沿路把pre数组连接起来。
例题1:uoj29 #79. 一般图最大匹配
求一般图最大匹配,并输出方案。
#include
#define ll long long
using namespace std;
const int N = 510;
const int M = 3e5+10;
struct node{ int to,nxt; }g[M];
int head[N],cnt;
int vis[N],match[N],f[N],pre[N],Id,id[N];
//vis[i]: 0(未染色) 1(黑色) 2(白色)
//match[i]: i的匹配点
//f[i]: i在带花树中的祖先
//pre[i]: i的非匹配边的另一点
//id: 找LCA用
int n,m,ans,u,v;
queue q;
void Init()
{
Id=ans=cnt=0;
for(int i=1;i<=n;i++)
head[i]=-1,id[i]=match[i]=0;
}
void add(int u,int v){ g[cnt]=node{v,head[u]},head[u]=cnt++; }
int getf(int x){ return f[x]==x?x:f[x]=getf(f[x]); }
//查询x和y在带花树中的LCA
int LCA(int x,int y)
{
//沿着增广路向上找lca
for(++Id;;swap(x,y))//x,y交替向上
if(x)
{
x=getf(x);//有可能环中有环(花中有花),所以用并查集找祖先,只处理祖先节点
if(id[x]==Id) return x; //x,y在同一环中,一定会找到已被编号的点,该点即为LCA。
else id[x]=Id,x=pre[match[x]];//给点编号,并沿着非匹配边向上找
}
}
//缩点(开花),将x、y到LCA(l)路径中的点,缩为一点
void blossom(int x,int y,int l)
{
while(getf(x)!=l)
{
//增广路取反
pre[x]=y,y=match[x];
//如果x、y的奇环中有白点,将其染为黑点,放入队列,让其去找不是环中的匹配点
if(vis[y]==2) vis[y]=1,q.push(y);
//只改变是根的点
if(getf(x)==x) f[x]=l;
if(getf(y)==y) f[y]=l;
//增广路取反
x=pre[y];
}
}
bool aug(int s)
{
//每次都以s为起点bfs,建带花树
for(int i=1;i<=n;i++)
vis[i]=pre[i]=0,f[i]=i;
while(!q.empty()) q.pop();
q.push(s),vis[s]=1;
while(!q.empty())
{
u=q.front();q.pop();
for(int i=head[u];~i;i=g[i].nxt)
{
v=g[i].to;
//如果已经在同一个环(花)中或者是白点(意为这已经有匹配点),只接跳过
//这种情况不会增加匹配数
if(getf(u)==getf(v)||vis[v]==2) continue;
//如果没有被染色
if(!vis[v])
{
//先染为白色,将前继点指向u
vis[v]=2,pre[v]=u;
//如果没有被匹配过,直接匹配成功
if(!match[v])
{
//增广路取反
for(int x=v,last;x;x=last)
last=match[pre[x]],match[x]=pre[x],match[pre[x]]=x;
return 1;
}
//如果被匹配过,则把匹配v的点染为黑色,放入队列中
vis[match[v]]=1,q.push(match[v]);
}
//v是黑色,形成奇环,则缩点(开花)。
else
{
int lca=LCA(u,v);
blossom(u,v,lca),blossom(v,u,lca);
}
}
}
return 0;
}
int main(void)
{
while(~scanf("%d%d",&n,&m))
{
Init();
while(m--)
{
scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
for(int i=1;i<=n;i++)
if(!match[i]&&aug(i))
ans++;
printf("%d\n",ans);
for(int i=1;i<=n;i++)
printf("%d%c",match[i]," \n"[i==n]);
}
return 0;
}
例题2:zoj3316 Game
题意:一个棋盘上有n个棋子,两个人做游戏,拿的棋子和上一个被拿的棋子的曼哈顿距离不能超过L,问后手能否赢。
思路:最大匹配等于n的时候才能赢,不然就会输。
#include
#define ll long long
using namespace std;
const int N = 400;
const int M = 2e5+10;
struct node{ int to,nxt; }g[M];
struct Node{ int x,y; }a[N];
int head[N],cnt;
int vis[N],pre[N],f[N],match[N],id[N],Id;
queue q;
int n,L,ans,u,v;
void Init()
{
ans=Id=cnt=0;
for(int i=1;i<=n;i++)
head[i]=-1,id[i]=match[i]=0;
}
void add(int u,int v) { g[cnt]=node{v,head[u]},head[u]=cnt++; }
int getf(int x){ return f[x]==x?x:f[x]=getf(f[x]); };
int LCA(int x,int y)
{
for(++Id;;swap(x,y))
if(x)
{
x=getf(x);
if(id[x]==Id) return x;
else id[x]=Id,x=pre[match[x]];
}
}
void blossom(int x,int y,int l)
{
while(getf(x)!=l)
{
pre[x]=y,y=match[x];
if(vis[y]==2) vis[y]=1,q.push(y);
if(getf(x)==x) f[x]=l;
if(getf(y)==y) f[y]=l;
x=pre[y];
}
}
bool bfs(int s)
{
for(int i=1;i<=n;i++)
vis[i]=pre[i]=0,f[i]=i;
while(!q.empty()) q.pop();
q.push(s);vis[s]=1;
while(!q.empty())
{
u=q.front();q.pop();
for(int i=head[u];~i;i=g[i].nxt)
{
v=g[i].to;
if(getf(u)==getf(v)||vis[v]==2) continue;
if(!vis[v])
{
vis[v]=2,pre[v]=u;
if(!match[v])
{
for(int x=v,last;x;x=last)
last=match[pre[x]],match[x]=pre[x],match[pre[x]]=x;
return 1;
}
vis[match[v]]=1,q.push(match[v]);
}
else
{
int lca=LCA(u,v);
blossom(u,v,lca),blossom(v,u,lca);
}
}
}
return 0;
}
int main(void)
{
while(~scanf("%d",&n))
{
Init();
for(int i=1;i<=n;i++)
scanf("%d%d",&a[i].x,&a[i].y);
scanf("%d",&L);
for(int i=1;i<=n;i++)
for(int j=1;j