模板题:uoj79
所谓的“花”,就是一个有奇数个点的环。对于这样的环,我们可以把它看作一个点,在环上就是一个点搭配着另一个点,这样一定会有一个点单出来,要和该花以外的点匹配,这个单身狗就是“花根(花托)”。
bfs找增广路。
假设现在有一个点x还没有找到伙伴,我们先给他钦定一个颜色,姑且认为是黑色,然后往前找。
如果下一个点y是一个已经被访问过的白点或者与x在同一个花中的黑点,跳过不处理。
如果这是一个没有被访问过的点,并且它已经有小伙伴了。x想和y组队,于是去和y的小伙伴z商量,看看这个z能不能找其他小伙伴。z说他可以先试一试,于是x和y之间用一个叫做pre的指针连一下,表示如果z找到了其他朋友,x和y可以考虑做朋友。再将z染成黑色,丢进队列里继续bfs找伙伴。
这个时候,我们找到了一个没有被访问过且没有小伙伴的点t,这就很令人高兴了,先用pre将z和t连起来,我们就让z和t做朋友,y和x做朋友……相当于将以t开头的这一串用pre和cp(匹配指针)连起来的点,cp全部消掉,pre全部变成cp。
while(y) {
int kl=cp[pre[y]];
cp[y]=pre[y],cp[pre[y]]=y,y=kl;
}
以上的情况和匈牙利算法也差不多,pre和cp交错的路径就是增广路。
现在问题来了,如果x同学找到了一个黑点y,怎么办呢?
如果一个黑点可以找一个黑点,说明这两个黑点在同一朵花中,所以我们要把它们原来所在的花合并到一朵新花中,这就叫做开花。
首先,我们找到x和y的花上LCA,就是它们向着花托方向走第一个能走到的点。
什么是花上LCA呢,因为一张图可能是一环套一环的,交错复杂的。如果出现x在花A里,y在花B里,花A缩成点后又在花C里,花B又在花D里,花C和花D都在花E里,这样E就是x和y的花LCA。
怎么求LCA?倍增?tarjan?花链剖分?
直接往根跳的时候暴力打标记,遇到打了标记的点说明遇到了LCA。我们知道一条增广路是pre和cp交错的,所以我们也应该交错走pre和cp。
int lca(int x,int y) {
x=find(x),y=find(y),++tim;
while(vis[x]!=tim) {
vis[x]=tim,x=find(pre[cp[x]]);//find:每次请走到花托上
if(y) swap(x,y);
}
return x;
}
而开花的过程,就是除了花托与其中一个和花托相连的点(因为x和y是两个黑点相连,所以它们其中有一个是花托,它们之间的pre边是单向的)之外,其他的边上的pre都要连成双向的,这样方便我们在寻找到与该花有交的增广路后进行增广。
如果存在不经过花托的增广路,那么我们不可能走到要让x和y两个黑点相连的地步,所以与该花有交的增广路一定经过花托。
花托是一个黑点,与外部白点匹配,所以与花有交的增广路一定经过花托和另一个白点。
所以我们在开花的时候,遇到白点,就要将其丢进队列里继续增广。
void bloss(int x,int y,int o) {
while(find(x)!=o) {//花托点不会进入该循环,所以pre就是单向的
pre[x]=y,y=cp[x];
if(bj[y]==2) bj[y]=1,q.push(y);//bj:颜色,2是白色1是黑色
if(x==find(x)) f[x]=o;
if(y==find(y)) f[y]=o;
x=pre[y];
}
}
总代码:
#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
const int N=502,M=250000;
int n,m,tot,tim,ans;
int h[N],ne[M],to[M],cp[N],pre[N],f[N],bj[N],vis[N];
//0:未访问 1:黑点 2:白点
queue<int> q;
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
int find(int x) {return x==f[x]?x:f[x]=find(f[x]);}
int lca(int x,int y) {//some questions
++tim,x=find(x),y=find(y);
while(vis[x]!=tim) {
vis[x]=tim,x=find(pre[cp[x]]);//find:每次请走到花托上
if(y) swap(x,y);
}
return x;
}
void bloss(int x,int y,int o) {
while(find(x)!=o) {
pre[x]=y,y=cp[x];
if(bj[y]==2) bj[y]=1,q.push(y);//如果路上遇到白点
if(find(x)==x) f[x]=o;
if(find(y)==y) f[y]=o;
x=pre[y];
}
}
int bfs(int st) {
while(!q.empty()) q.pop();
for(RI i=1;i<=n;++i) bj[i]=pre[i]=0,f[i]=i;
bj[st]=1,q.push(st);
while(!q.empty()) {
int x=q.front();q.pop();
for(RI i=h[x];i;i=ne[i]) {
int y=to[i];
if(find(x)==find(y)||bj[y]==2) continue;
if(!bj[y]) {
bj[y]=2,pre[y]=x;
if(!cp[y]) {
while(y) {
int kl=cp[pre[y]];
cp[y]=pre[y],cp[pre[y]]=y,y=kl;
}
return 1;
}
q.push(cp[y]),bj[cp[y]]=1;
}
else {int o=lca(x,y);bloss(x,y,o),bloss(y,x,o);}
}
}
return 0;
}
int main()
{
int x,y;
n=read(),m=read();
for(RI i=1;i<=m;++i) x=read(),y=read(),add(x,y),add(y,x);
for(RI i=1;i<=n;++i) if(!cp[i]) ans+=bfs(i);
printf("%d\n",ans);
for(RI i=1;i<=n;++i) printf("%d ",cp[i]);
puts("");
return 0;
}