【图论】【二分图匹配】匈牙利与km模板
Singercoder · · 个人记录
前排提醒
在熟练掌握匈牙利相关算法的dfs写法后,一定要改进为bfs。
km算法bfs写法可以降复杂度
后续学习的带花树算法,核心思想只能用bfs实现,使得路径相对确定。(也可能只是我不会dfs写法,欢迎交流)
匈牙利
uoj78
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
inline int read()
{
int ret=0;char ch=getchar();
while(ch<'0' || ch>'9')ch=getchar();
while(ch>='0' && ch<='9')
{
ret=ret*10+ch-'0';
ch=getchar();
}
return ret;
}
const int MAXN=510;
int n[2],m,ans;
bool e[MAXN][MAXN];
void input()
{
n[0]=read();n[1]=read();m=read();
for(int i=1;i<=m;++i)e[read()][read()]=1;
}
/*命名原则:男生为出点u 女生为入点v*/
int px[MAXN],py[MAXN];//记录匹配
int pre[MAXN];//记录女生(入点)的前驱
bool vx[MAXN],vy[MAXN];
queue<int> q;
void aug(int v)
{
while(v)
{
int t=px[pre[v]];
px[pre[v]]=v;
py[v]=pre[v];
v=t;
}
}
bool bfs(int s)
{
memset(pre,0,sizeof(pre));//初始化路径
memset(vx,0,sizeof(vx));//初始化vis
memset(vy,0,sizeof(vy));
while(!q.empty())q.pop();//清空队列
q.push(s);
int u;
while(!q.empty())
{
u=q.front();q.pop();
vx[u]=1;
for(int v=1;v<=n[1];++v)if(e[u][v] && !vy[v])
{
vy[v]=1;
pre[v]=u;//记录路径
if(!py[v]){aug(v);return 1;}//找到増广路终点
else q.push(py[v]);//v已有匹配,令u=match[v]
}
}
return 0;//増广失败
}
void solve()
{
for(int i=1;i<=n[0];++i)if(bfs(i))++ans;
}
void output()
{
printf("%d\n",ans);
for(int i=1;i<=n[0];++i)printf("%d ",px[i]);
printf("\n");
}
int main()
{
// freopen("in.txt","r",stdin);
input();
solve();
output();
return 0;
}
km
uoj80
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define ll long long
#define inf 0x3fffffff
using namespace std;
inline int read()
{
int ret=0;char ch=getchar();
while(ch<'0' || ch>'9')ch=getchar();
while(ch>='0' && ch<='9')
{
ret=ret*10+ch-'0';
ch=getchar();
}
return ret;
}
const int MAXN=510;
int n[2],m;
int e[MAXN][MAXN];
void input()
{
n[0]=read();n[1]=read();m=read();
if(n[1]<n[0])n[1]=n[0];//添加虚点,保证每个男生都有配偶
for(int i=1;i<=m;++i)e[read()][read()]=read();
}
/*命名原则:男生为出点u 女生为入点v*/
int px[MAXN],py[MAXN];//记录匹配
int lx[MAXN],ly[MAXN];//记录顶标
int pre[MAXN];//记录女生(入点)的前驱
int slack[MAXN],d;//记录边权与顶标的最小差值,所有slack中的最小值
bool vx[MAXN],vy[MAXN];
queue<int> q;
void aug(int v)
{
int t;
while(v)
{
t=px[pre[v]];
px[pre[v]]=v;
py[v]=pre[v];
v=t;
}
}
void bfs(int s)
{
fill(slack+1,slack+n[1]+1,inf);
memset(pre,0,sizeof(pre));
memset(vx,0,sizeof(vx));
memset(vy,0,sizeof(vy));
while(!q.empty())q.pop();
q.push(s);
int u;
while(1)
{
while(!q.empty())
{
u=q.front();q.pop();
vx[u]=1;
for(int v=1;v<=n[1];++v)if(!vy[v])//枚举可増广v
{
if(lx[u]+ly[v]-e[u][v]<slack[v])//増广路径选择slack最小的
{
slack[v]=lx[u]+ly[v]-e[u][v];
pre[v]=u;//记录v的前驱
if(!slack[v])//边在相等子图中,可以増广
{
vy[v]=1;
if(!py[v]){aug(v);return;}//找到増广路终点
else q.push(py[v]);//v已有匹配,令v=py[v]
}
}
}
}
//増广失败,扩大子图
d=inf;
for(int i=1;i<=n[1];++i)if(!vy[i])d=min(d,slack[i]);//寻找所有差值中最小的
if(d==inf)break;//扩大失败
//修改顶标
for(int i=1;i<=n[0];++i)if(vx[i])lx[i]-=d;
for(int i=1;i<=n[1];++i)if(vy[i])ly[i]+=d;else slack[i]-=d;
//将新可増广点入队,为下次増广作准备
for(int i=1;i<=n[1];++i)if(!vy[i] && !slack[i])
{
vy[i]=1;
if(!py[i]){aug(i);return;}//找到増广路终点
else q.push(py[i]);//v已有匹配,令v=py[v]
}
}
}
void km()
{
for(int u=1;u<=n[0];++u)
for(int v=1;v<=n[1];++v)
lx[u]=max(lx[u],e[u][v]);//初始化lx
for(int i=1;i<=n[0];++i)bfs(i);
}
void output()
{
ll ans=0;
for(int i=1;i<=n[0];++i)ans+=lx[i];
for(int i=1;i<=n[1];++i)ans+=ly[i];
printf("%lld\n",ans);
for(int i=1;i<=n[0];++i)if(e[i][px[i]])printf("%d ",px[i]);else printf("0 ");
printf("\n");
}
int main()
{
// freopen("in.txt","r",stdin);
input();
km();
output();
return 0;
}