[KM算法/坤M算法] 二分图带权匹配

坤M算法有它的局限性:要求带权最大匹配一定是完备匹配
但是效率高啊:坤M算法 Θ ( n 3 ) \Theta(n^3) Θ(n3),费用流 Θ ( 能 过 ) \Theta(能过) Θ()

奇奇怪怪的定义:

  • 顶标 顶点标记值,记左部 i i i节点顶标 A i A_i Ai,右部 j j j节点顶标 B j B_j Bj,满足 A i + B j ≥ w i ↔ j A_i+B_j\geq w_{i\leftrightarrow j} Ai+Bjwij
  • 交错树 从左部节点出发寻找匹配失败,DFS过程中访问的所有节点构成交错树
    交错树层与层之间的边以匹配边与非匹配边的形式交错出现
  • 相等子图 ∑ i ∈ S A , j ∈ S B A i + B j = ∑ i ∈ S A , j ∈ S B w i ↔ j \sum_{i\in S_A,j\in S_B}A_i+B_j=\sum_{i\in S_A,j\in S_B}w_{i\leftrightarrow j} iSA,jSBAi+Bj=iSA,jSBwij

结论:当每个相等子图完备匹配时,二分图有最大匹配
证明:对于二分图 ∑ i ∈ S A , j ∈ S B A i + B j ≥ ∑ i ∈ S A , j ∈ S B w i ↔ j \sum_{i\in S_A,j\in S_B}A_i+B_j\geq\sum_{i\in S_A,j\in S_B}w_{i\leftrightarrow j} iSA,jSBAi+BjiSA,jSBwij,而现在边权和已经抵到最大值即顶标之和了,不能再大

算法:
分配顶标,左部令为边权( A i = max ⁡ { w i ↔ j } A_i=\max\{w_{i\leftrightarrow j}\} Ai=max{wij}),右部令为零
修改顶标,找不到相等子图的时候就修改
修改顶标时不会改变现有匹配的权值,算法具有正确性
修改顶标直到找到完备匹配

考虑修改顶标
发现DFS时左部到右部走的都是非匹配边,右部到左部走的都是匹配边
而走匹配边无需修改顶标
考虑走非匹配边的修改顶标
若从左部点走到右部点,以前能访问,现在仍能访问,则修改没有作用
而若以前右部点不在匹配(记为 T T T)中,现在在,则需要考虑加入此边
即在左部点 i ∈ T i\in T iT,右部点 j ∉ T j\notin T j/T(可能是众点中的一个),加入这条边,构造出相等子图的边 A i + B j = w i ↔ j A_i+B_j=w_{i\leftrightarrow j} Ai+Bj=wij
而原来 A i + B j ≥ w i ↔ j A_i+B_j\geq w_{i\leftrightarrow j} Ai+Bjwij,因此减少 A i + B j − w i ↔ j A_i+B_j-w_{i\leftrightarrow j} Ai+Bjwij,且要求它最小
注意,改变值的是交错树中顶标,非交错树中的顶标不变

板题
不知为何会WA但是是照着标程打的代码

#include<bits/stdc++.h>
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') ch=getchar();
	while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

typedef long long ll;
const int N=505;
const int INF=2147483647;
int n,m;
int w[N][N],mat[N];
int la[N],lb[N],upd[N],delta;
bool va[N],vb[N];
// upd for updating delta, which if b\notin T

bool DFS(int u){
	va[u]=true;
	for(int v=1;v<=n;++v){
		if(!vb[v])
			if(la[u]+lb[v]-w[u][v]) upd[v]=min(upd[v],la[u]+lb[v]-w[u][u]);
			else{
				vb[v]=true;
				if(!mat[v]||DFS(mat[v])){
					mat[v]=u;
					return true;
				}
			}
	}
	return false;
}

ll KM(){
	for(int i=1;i<=n;++i){
		la[i]=-INF;
		lb[i]=0;
		for(int j=1;j<=n;++j)
			la[i]=max(la[i],w[i][j]);
	}
	for(int i=1;i<=n;++i)
		// Until get the match
		// modify topmark each time
		while(true){
			memset(va,0,sizeof(va));
			memset(vb,0,sizeof(vb));
			for(int j=1;j<=n;++j) upd[j]=INF;
			if(DFS(i)) break;
			for(int j=1;j<=n;++j)
				if(!vb[j]) delta=min(delta,upd[j]);
			for(int j=1;j<=n;++j){
				if(va[j]) la[j]-=delta;
				if(vb[j]) lb[j]+=delta;
			}
		}
	ll ans=0;
	for(int i=1;i<=n;++i) ans+=w[mat[i]][i];
	return ans;
}

int main(){
	n=in,m=in;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			w[i][j]=-INF;
	for(int i=1;i<=n;++i){
		int u=in,v=in,val=in;
		w[u][v]=max(w[u][v],val);
	}
	printf("%lld\n",KM());
	for(int i=1;i<=n;++i) printf("%d ",mat[i]);
	return 0;
}

优秀的BFS不想码了
这DFS就够恶心了
再加上我看不懂yyr巨巨的代码😭

好吧,坤M就是这样

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值