学习笔记第十二节:二分图最优匹配

正题

      看到这个题目,会觉得可以直接用绿与被绿匈牙利算法来解决。

      但是当我们遇到,“第i个人和第j个物品会产生g[i][j]的价值,求完全匹配的最小价值”的时候。

      我们就需要用到二分图最优匹配的算法了。

      这个算法的复杂度很高,要慎用

      那么其实也是可以用费用流来解决的,直接建边,每次跑SPFA即可。

      回归主题:怎么求?

      从这里开始认真看。设置两个数组,tx_ity_i,分别表示tx指左边,ty指右边。

      我们定义当且仅当tx_x+ty_i==g[x][i] 的时候,我们才认为从x可以到i。

bool find(int x){
	visx[x]=true;
	for(int i=1;i<=n;i++)
		if(!visy[i] && tx[x]+ty[i]==g[x][i]){//匈牙利的这一行发生改变
			visy[i]=true;
			if(prep[i]==0 || find(prep[i])){
				prep[i]=x;
				return true;
			}
		}
	return false;
}

      那么我们要像费用流那样子,每次优先走最大的边。

      这样走出来的增广路是最大的。

      所以,每个tx_i初始化为第i个点出边的最大,也就是tx_i=\sum_{j=1}^n max(g[i][j])

      这样我们走出来的是最大的。

      但是可能很多个点会选择同一个点。

       那怎么办呢。

       我们可以让某些tx和ty改变使得可以走更多的边。

       因为我们走某条边的条件,所以明显当时tx_i+=d,那些可以走去的右节点的ty就要-=d.

       目的是使得“之前的点可以走”。

       我们肯定要想着变化量越小越好,所以我们在去不到的边取一个\Delta最小值。

       再不断地用匈牙利增广就行了。

bool find(int x){
	visx[x]=true;
	for(int i=1;i<=n;i++)
		if(!visy[i] && tx[x]+ty[i]==g[x][i]){//如果符合标杆就更新
			visy[i]=true;
			if(prep[i]==0 || find(prep[i])){
				prep[i]=x;
				return true;
			}
		}
	return false;
}

int KM(){
	memset(tx,0,sizeof(tx));
	memset(ty,0,sizeof(ty));
	memset(prep,0,sizeof(prep));
	int mmin=1e9;
	for(int i=1;i<=n;i++){
		while(1){
			memset(visx,false,sizeof(visx));
			memset(visy,false,sizeof(visy));
			if(find(i)) break;
			mmin=1e9;
			for(int j=1;j<=n;j++) if(visx[j])//更新那些去不到的点
				for(int k=1;k<=n;k++) if(!visy[k]) mmin=min(mmin,tx[j]+ty[k]-g[j][k]);
			for(int j=1;j<=n;j++) if(visx[j]) tx[j]-=mmin;//更新标杆
			for(int j=1;j<=n;j++) if(visy[j]) ty[j]+=mmin;
		}
	}
	node op=(node){0,0};
    int ans=0;
	for(int i=1;i<=n;i++) ans+=g[prep[i]][i];
	return ans;
}

       有人想问我,为什么标杆tx和ty一开始设成0就行,因为第一次找不到之后,会更新成最大值(负数的绝对值越大,数值越小)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值