参考资料:https://rqy.moe/Algorithms/flower-tree/ https://www.cnblogs.com/owenyu/p/6858508.html
这个算法有点意思。要看更本质的理解和证明可以看 _rqy 的博客。
理论
由于有奇环的存在,匈牙利算法的理论不再适用。但是奇环也有一些性质,是可以被我们利用的。
仍然从每个点出发,尝试找一条增广路。这里我们用 BFS 来找。
在出现奇环之前, BFS 的过程可以被看做一棵 BFS 树,树上可能有返祖和横叉边。
在 BFS 的过程中,把沿途的点黑白染色。设出发点为黑色。黑点表示在原有的匹配中往上匹配,白点表示往下匹配。我们只从黑点出发找增广路。
每次从黑点 \(x\) 开始寻找增广路。普通情况比较好搞,重点考虑黑点 \(x\) 连向黑点 \(y\) 的边。
此时,我们找到了一个奇环。考虑这个奇环有什么性质:它在 BFS 树中深度最低的点,即 \(lca(x,y)\) ,一定是黑点。所以可以发现不管奇环中哪个点往外找到了一条增广路,都可以在环里面往某一个方向继续匹配。
如图, 2 往外找到了 2-6 的路径,那么可以补成 1-3-5-4-2-6 ,就成功地找到了增广路。
所以可以把环缩成一个点(\(lca(x,y)\)),环中所有点的出边都连到 \(lca(x,y)\) 上,然后继续找增广路。
可以证明新图中有增广路等价于原图中有增广路。(见 _rqy 博客)
但是朴素实现可能比较恶心,所以在实际操作中不会真的缩点。
实现
BFS ,过程中维护 \(pre\) 数组,表示找到增广路之后要往哪里跳。其另一个意义是如果 \(x\) 原有的匹配被占了,那么 \(x\) 现在应该匹配谁。
设当前点为 \(x\) ,连向的点为 \(v\) 。
如果 \(v\) 没有被遍历过,那么把它涂成白色。若 \(v\) 没有匹配那么说明找到了一条增广路,否则把 \(match_v\) 涂成黑色,并加入队列中。
否则,如果 \(v\) 是白色,或是已经和 \(x\) 在同一个奇环中(缩花的时候被缩在了一起),那么忽略它。
否则, \(v\) 是黑色,就说明找到了一个奇环,需要缩环。
先暴力找到 \(lca(x,v)\) ,然后分别把 \(x\to l\) 和 \(v \to l\) 的路径缩起来。
维护 \(fa_x\) 表示所在奇环的深度最低的点是哪个。由于可能环中有环, \(fa_x\) 用并查集维护。
缩的时候把环中所有白点都涂黑并加入队列,因为此时他们往外的增广路也是有用的。
同时还要更新环中点的 \(pre\) ,每个点的 \(pre\) 为与它相邻的非匹配边的点,如上图中 \(pre_5=4,pre_4=5,pre_3=1\) 。
然后继续做即可。
代码
#include
clock_t t=clock();
namespace my_std{
using namespace std;
#define pii pair
#define fir first
#define sec second
#define MP make_pair
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
#define drep(i,x,y) for (int i=(x);i>=(y);i--)
#define go(x) for (int i=head[x];i;i=edge[i].nxt)
#define templ template
#define sz 555
typedef long long ll;
typedef double db;
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
templ inline T rnd(T l,T r) {return uniform_int_distribution(l,r)(rng);}
templ inline bool chkmax(T &x,T y){return xy?x=y,1:0;}
templ inline void read(T& t)
{
t=0;char f=0,ch=getchar();double d=0.1;
while(ch>'9'||ch<'0') f|=(ch=='-'),ch=getchar();
while(ch<='9'&&ch>='0') t=t*10+ch-48,ch=getchar();
if(ch=='.'){ch=getchar();while(ch<='9'&&ch>='0') t+=d*(ch^48),d*=0.1,ch=getchar();}
t=(f?-t:t);
}
templateinline void read(T& t,Args&... args){read(t); read(args...);}
char __sr[1<<21],__z[20];int __C=-1,__zz=0;
inline void Ot(){fwrite(__sr,1,__C+1,stdout),__C=-1;}
inline void print(register int x)
{
if(__C>1<<20)Ot();if(x<0)__sr[++__C]='-',x=-x;
while(__z[++__zz]=x%10+48,x/=10);
while(__sr[++__C]=__z[__zz],--__zz);__sr[++__C]='\n';
}
void file()
{
#ifdef NTFOrz
freopen("a.in","r",stdin);
#endif
}
inline void chktime()
{
#ifndef ONLINE_JUDGE
cout<<(clock()-t)/1000.0<<'\n';
#endif
}
#ifdef mod
ll ksm(ll x,int y){ll ret=1;for (;y;y>>=1,x=x*x%mod) if (y&1) ret=ret*x%mod;return ret;}
ll inv(ll x){return ksm(x,mod-2);}
#else
ll ksm(ll x,int y){ll ret=1;for (;y;y>>=1,x=x*x) if (y&1) ret=ret*x;return ret;}
#endif
// inline ll mul(ll a,ll b){ll d=(ll)(a*(double)b/mod+0.5);ll ret=a*b-d*mod;if (ret<0) ret+=mod;return ret;}
}
using namespace my_std;
int n,m;
int e[sz][sz];
int fa[sz];
int getfa(int x){return x==fa[x]?x:fa[x]=getfa(fa[x]);}
int pre[sz],match[sz],tp[sz];
queueq;
int vis[sz],T;
int lca(int x,int y) { for (++T;;swap(x,y)) if (x) { x=getfa(x); if (vis[x]==T) return x; vis[x]=T,x=pre[match[x]]; } }
void shrink(int x,int y,int l)
{
while (getfa(x)!=l)
{
pre[x]=y,y=match[x];
if (tp[y]==2) tp[y]=1,q.push(y);
if (x==fa[x]) fa[x]=l;
if (y==fa[y]) fa[y]=l;
x=pre[y];
}
}
int work(int s)
{
while (q.size()) q.pop(); q.push(s);
rep(i,1,n) fa[i]=i,pre[i]=0,tp[i]=0;
tp[s]=1;
while (!q.empty())
{
int x=q.front();q.pop();
rep(v,1,n) if (e[x][v])
{
if (getfa(x)==getfa(v)||tp[v]==2) continue;
if (!tp[v])
{
pre[v]=x;
if (!match[v])
{
for (int cur=v;cur;swap(cur,match[pre[cur]])) match[cur]=pre[cur];
match[0]=0;
return 1;
}
tp[v]=2,tp[match[v]]=1,q.push(match[v]);
}
else { int l=lca(x,v); shrink(x,v,l),shrink(v,x,l); }
}
}
return 0;
}
int main()
{
file();
read(n,m);
int x,y;
rep(i,1,m) read(x,y),e[x][y]=e[y][x]=1;
int ans=0;
rep(i,1,n) rep(j,i+1,n) if (!match[i]&&!match[j]&&e[i][j]) match[i]=j,match[j]=i,++ans;
rep(i,1,n) ans+=(!match[i]&&work(i));
printf("%d\n",ans);
rep(i,1,n) printf("%d ",match[i]);
return 0;
}