正题
首先我们要了解几个概念。
顶标 txi,tyitx_i,ty_itxi,tyi 分别表示的是人为赋予的左右点的点权,在运算过程中,需要满足txu+tyv≥wu,vtx_u+ty_v\geq w_{u,v}txu+tyv≥wu,v
相等子图:每一个点与满足 txu+tyv=wu,vtx_u+ty_v=w_{u,v}txu+tyv=wu,v 的边构成的子图。
定理:若相等子图存在完美匹配,则这个完美匹配的权值就是最大的。
证明:首先对于任意一个完美匹配的值满足:∑e∈Awe≤∑i=1ntxi+tyi\sum_{e\in A}w_e\leq \sum_{i=1}^n tx_i+ty_ie∈A∑we≤i=1∑ntxi+tyi
其次,若相等子图存在完美匹配,则该匹配满足:∑e∈Awe=∑i=1ntxi+tyi\sum_{e\in A}w_e = \sum_{i=1}^n tx_i+ty_ie∈A∑we=i=1∑ntxi+tyi
我们先来讨论一下较为简单的算法流程。
首先将左边点的顶标 txitx_itxi 设置为 iii 出边中的权值最大值, tyity_ityi 设为 000 。
接着依次枚举各个点 iii ,再枚举这个点的出边,如果出边对应的点 jjj 满足:txi+tyj=wi,jtx_i+ty_j=w_{i,j}txi+tyj=wi,j
就讨论:
1.若jjj点没有匹配的点,那么连边 returnreturnreturn 就可以了。
2.否则就dfsdfsdfs j的匹配点,完成上述步骤。
如果最后能找到一条增广路,那么说明当前的顶标就是可行的。
否则我们要尝试另外一种顶标方案使其可以找到增广路。
我们用两个bool数组 vx,vyvx,vyvx,vy 分别记录左右两边点是否被遍历过。
由于增广不成功,增广路上面的肯定是一些左右点的可行匹配。
设 ddd 是满足左边的点被遍历了,右边的点没有被遍历的边中 txi+tyj−wi,jtx_i+ty_j-w_{i,j}txi+tyj−wi,j 最小的。
考虑将遍历过的左边点的 txi=txi−dtx_i=tx_i-dtxi=txi−d ,右边点的 tyj=tyj+dty_j=ty_j+dtyj=tyj+d 。
对于一条边来说:
1.若左右两边点都被遍历过, txi+tyj−wi,jtx_i+ty_j-w_{i,j}txi+tyj−wi,j 不会变,是否匹配的状态也不会变。
2.若左右两边点都没有被遍历过,同上。
3.若左边的点被遍历了,右边的点没有, txi+tyj−wi,jtx_i+ty_j-w_{i,j}txi+tyj−wi,j 变小,这条边可能会被加入相等子图中。
4.若左边的点没有被遍历,有边的点有,txi+tyj−wi,jtx_i+ty_j-w_{i,j}txi+tyj−wi,j 变大,这条边不可能被加入相等子图中。
实际上我们更关注的是第三点,因为一条失败的增广路都是走到左边点停了。
但因为我们想要使得相等子图存在增广路的同时,顶标和尽可能大,所以自然就想让 ddd 小一些。
上述做法时间复杂度瓶颈在于每次都需要重新找增广路,每次时间复杂度 O(n+m)O(n+m)O(n+m) 。
总时间复杂度就变成了 O(n2(n+m))O(n^2(n+m))O(n2(n+m)) 。
实际上有更好的方法。
考虑 bfsbfsbfs ,用一个 slackslackslack 数组表示一个右边点所连边中 txi+tyj−wi,jtx_i+ty_j-w_{i,j}txi+tyj−wi,j 最小值。
这样我们只需要将可能增广的左节点不断加入队列,考虑当前左端点所连右端点,更新其slack值。
若满足该点 slack=0slack=0slack=0 ,就可以观察这个右端点是否有匹配点,如果没有,那么就找到了合法增广路,如果有,就将那个匹配点加入队列。
每次我们寻找不在增广路上的 slackslackslack 最小值即可作为 ddd 值,更新就可以了,在此过程中,不用每次都重新构建,时间复杂度大大减小,总共有 nnn 个点进行增广,只会更改 nnn 次顶标,每次更改时间复杂度 O(n)O(n)O(n) ,时间复杂度 O(n3)O(n^3)O(n3) 。
#include<bits/stdc++.h>
using namespace std;
const int N=510;
bool vx[N],vy[N];
long long tx[N],ty[N],slc[N],w[N][N];
int mx[N],my[N],pre[N],n,m;
int qs[N],st,ed;
bool check(int y){
vy[y]=true;
if(my[y]){
vx[my[y]]=true;
qs[++ed]=my[y];
return false;
}
while(y){
my[y]=pre[y];
swap(mx[my[y]],y);
}
return true;
}
void find(int x){
qs[st=ed=1]=x;vx[x]=true;
while(1){
while(st<=ed){
int x=qs[st++];
for(int y=1;y<=n;y++) if(w[x][y]!=-20000000 && !vy[y]){
long long d=tx[x]+ty[y]-w[x][y];
if(d<slc[y]){
pre[y]=x;
if(d) slc[y]=d;
else if(check(y)) return ;
}
}
}
long long mmin=slc[0];
for(int i=1;i<=n;i++) if(!vy[i]) mmin=min(mmin,slc[i]);
for(int i=1;i<=n;i++){
if(vx[i]) tx[i]-=mmin;
if(vy[i]) ty[i]+=mmin;
else slc[i]-=mmin;
}
for(int i=1;i<=n;i++)
if(!vy[i] && !slc[i] && check(i)) return ;
}
}
void solve(){
for(int i=1;i<=n;i++) tx[i]=-20000000;
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)
tx[i]=max(tx[i],w[i][j]);
for(int i=1;i<=n;i++){
memset(vx,false,sizeof(vx));
memset(vy,false,sizeof(vy));
memset(slc,63,sizeof(slc));
find(i);
}
long long ans=0;
for(int i=1;i<=n;i++) ans+=tx[i]+ty[i];
printf("%lld\n",ans);
for(int i=1;i<=n;i++) printf("%d ",my[i]);
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) w[i][j]=-20000000;
int x,y,c;
for(int i=1;i<=m;i++) scanf("%d %d %d",&x,&y,&c),w[x][y]=c;
solve();
}
本文详细介绍了最大权匹配算法的原理,包括顶标的概念、相等子图的定义及完美匹配定理。通过逐步解析算法流程,阐述了如何初始化顶标、寻找增广路径以及如何调整顶标以找到最优解。特别地,提出了一种使用bfs优化算法的时间复杂度,从O(n^2(n+m))降低到O(n^3)。
719

被折叠的 条评论
为什么被折叠?



