由来
2019 年南京 Regional 充分暴露了这个问题,市面上大多数标着 O(n3) 的 KM 板子实际上是 O(n4) 的,以致选手如果是用了经典书籍上的板子,或者是网上随便扒的板子,就会 TLE。
然后最近做题做到了 KM,就想补一个自己的真·O(n3) 的 KM。网上的 O(n3) 也都没有教程只能自己啃代码,就想把思路写一下。
大二了才会 KM 你丢不丢人
一个简单粗暴的判断板子是 O(n4) 还是 O(n3) 的办法:非递归是 O(n3),递归(用了匈牙利一样的东西)是 O(n4)。
更加正确的判断方法:拿去交正确的模板题,比如 uoj#80、2019 南京 Regional J(计蒜客复现)。
前置
要解决的是二分图最大权匹配问题。这是一个线性规划问题,它的对偶问题是最小顶标和问题,KM 的思路就是计算最小顶标和。
O(n4)
看这位大佬生动的解释就懂了:https://www.cnblogs.com/wenruo/p/5264235.html
匈牙利是 O(n2) 的,修改顶标也是 O(n2) 的,每个点最多 n 次修改顶标,共 n 个点,因此是 O(n4)。
O(n3)
现在是对于左边的点 i,我们来优化它的匹配过程。按照 O(n4) 的思想,我们是每次匈牙利找增广路,失败了就修改顶标。
如果我们默认每一次增广都会失败,那么实际上每一次增广的目的就成了最小化 d(最小的顶标增量使得相等子图能新加入一条边)。我们把这个 d 挂在右边的结点上,记为 slackj,表示增广路最后到达的右边结点为 j 时,能达到的最小的 d。
那么这就如同 Dijkstra,我们从 i 出发,把右边的结点的 slack 全部松弛一遍,然后找到 slack 最小的 j,slackj 就是本次的顶标增量。(若 slackj=0 则顶标不动,相当于匈牙利继续,若 slackj>0 相当于匈牙利失败了要修改顶标。)
然后尝试匹配 i 和 j。若 j 单身,则牵手成功;若 j 名花有主,则尝试绿掉原配增广原配,即从原配出发,把右边的结点的 slack 又松弛一遍……
直到最后找到了单身的 j,就增广成功了。
于是每个 j 只会被尝试匹配一次,每个原配只会被绿一次增广一次,这样就相当于 i 只做了一次如同 BFS 般的多路增广,所以总时间复杂度为 O(n3)。
回头看我们默认每一次都增广失败,实际上如果增广成功的话就意味着一路 slackj=0,这种情况被包括了。
我觉得把它称为多路增广,或者 BFS,都不如 Dijkstra 贴切。
代码
UPD:区分了左边点数 nl 和右边点数 nr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| LL lx[maxn],ly[maxn],slack[maxn]; int f[maxn],pre[maxn]; bool vis[maxn]; LL KM() { fo(i,1,nl) { lx[i]=-inf; fo(j,1,nr) lx[i]=max(lx[i],mp[i][j]); } fo(i,1,nl) { memset(slack,127,sizeof(LL)*(nr+1)); memset(vis,0,sizeof(bool)*(nr+1)); f[0]=i; int py=0, nextpy; for(; f[py]; py=nextpy) { int px=f[py]; LL d=inf; vis[py]=1; fo(j,1,nr) if (!vis[j]) { if (lx[px]+ly[j]-mp[px][j]<slack[j]) slack[j]=lx[px]+ly[j]-mp[px][j], pre[j]=py; if (slack[j]<d) d=slack[j], nextpy=j; } fo(j,0,nr) if (vis[j]) lx[f[j]]-=d, ly[j]+=d; else slack[j]-=d; } for(; py; py=pre[py]) f[py]=f[pre[py]]; } LL re=0; fo(i,1,nl) re+=lx[i]; fo(i,1,nr) re+=ly[i]; return re; }
int main() { printf("%lld\n",KM()); }
|