二分图带权匹配KM

二分图带权匹配的前提是匹配数最大,KM算法的前提是带权最大匹配一定是完备匹配。

顶点标记值,给左部点一个整数值a[i],给右部点一个整数值b[i],同时必须满足对于任意的i,j,a[i]+b[j]>=w(i,j),w(i,j)为i与j之间的边权,无边是为负无穷,a[i]和b[i]称为节点的顶标。

若二分图中所有节点和满足a[i]+b[i]=w(i,j),该子图称为二分图的相等子图。

若相等子图中存在完备匹配,则这个完备匹配就是二分图的带权最大匹配。

KM基本思想是在满足对于任意i,j,a[i]+b[j]>=w(i,j)的前提下,给每个顶点随意赋值个顶标,然后采取适当策略不断扩大相等子图的规模直至存在完备匹配。通常赋值a[i]=max(1<=j<=n){w(i,j)},b[j]=0.

对相等子图用匈牙利算法求最大匹配,若不完备,则说明一定有一个左部点匹配失败,记该点匹配失败那次dfs构成的交错树为t,在所有i处于t,j不属于t的(i,j)中找到最小的a[i]+b[j]-w(i,j),作为delta,所有左部点减少delta,所有右部点增加delta,从前i访问不到的j现在可能访问到了,不断重复直至每一个左部点都匹配成功。

进一步优化,已经遍历过的交错树没有必要再次遍历,只需要从a[i]+b[j]-w(i,j)最小的边,即新加入相等子图的边开始搜索,在已有交错树上继续扩展。记录last倒推找出增广路,时间复杂度O(N^3)。

如果要求最小权值和的完备匹配,将每条边取反,最后输出答案的相反数即可。

bool dfs(int x,int fa){
   vx[x]=1;/*x在交错树中*/
   for(int y=1;y<=n;y++){
   	if(vy[y])continue;
   	if(lx[x]+ly[y]==w[x][y]){/*相等子图,相当于这两点间有边*/
   		vy[y]=1;/*y在交错树中*/
   		last[y]=fa;/*记录路径*/
   		if(!match[y]||dfs(match[y],y)){
   			match[y]=x;
   			return true;
   		}
   	}
   	else if(upd[y]>lx[x]+ly[y]-w[x][y]){
   		upd[y]=lx[x]+ly[y]-w[x][y];/*记录最小delta值,y最少增加upd[y]则可以得到匹配,即y和左部点有边的最小条件*/
   		last[y]=fa;
   	}
   }
   return false;
}
inline int km(){
   memset(match,0,sizeof(match));
   for(int i=1;i<=n;i++){
   	lx[i]=-INF,/*初始化负无穷*/ly[i]=0;
   	for(int j=1;j<=n;j++)lx[i]=max(lx[i],w[i][j]);
   }
   for(int i=1;i<=n;i++){
   	memset(vx,0,sizeof(vx));
   	memset(vy,0,sizeof(vy));
   	memset(last,0,sizeof(last));
   	memset(upd,0x3f,sizeof(upd));/*初始化正无穷*/
   	int st=0;match[0]=i;/*从右部st匹配的左部点开始dfs,开始假设有一条0-i的匹配*/
   	while(match[st]){/*到达第一个非匹配点st则停止*/
   		int delta=INF;/*最小可降低的值*/
   		if(dfs(match[st],st))break;
   		for(int j=1;j<=n;j++)if(!vy[j]/*j不在交错树中*/&&delta>upd[j]/*新的delta值*/)delta=upd[st=j];/*下次从j开始*/
   		for(int j=1;j<=n;j++){
   			if(vx[j])lx[j]-=delta;/*访问过的左部点减少delta*/
   			if(vy[j])ly[j]+=delta;/*访问过的右部点增加delta*/
   			else upd[j]-=delta;/*没有访问过的右部点,因为左部点的顶标减少,距离被访问又近了一步,所以减少delta*/
   		}
   		vy[st]=1;/*下一次从st开始dfs,但是st不会被标记在交错树中,所以在这里标记*/
   	}
   	while(st){/*倒推出增广路*/
   		match[st]=match[last[st]];
   		st=last[st];
   	}
   }
   int ans=0;
   for(int i=1;i<=n;i++)ans+=w[match[i]][i];
   return ans;
}

男女队员各n人,两个nn矩阵矩阵p和q,p[i][j]表示男i和女j配合的优势,q[i][j]表示女i和男j配合的优势,p[i][j]不一定等于q[j][i],双方配合的优势为p[i][j]q[j][i],求最大优势总和。
输入其中一个矩阵,另一个矩阵输入时交换坐标相乘,跑km即可。

    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)cin>>w[i][j];
   for(int i=1;i<=n;i++)for(int j=1,x;j<=n;j++)cin>>x,w[j][i]*=x;

n各仓库n中货物,把相同的货物放到同一个仓库,每一次搬运货物的代价等于搬运货物的重量,求最小总代价。将i货物放到j仓库的代价就是sum[i]-mp[j][i],边权取反跑km即可。

	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){
   	int x;cin>>x;
   	a[i][j]=x;sum[j]+=x;
   }
   for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)w[i][j]=-(sum[i]-a[j][i]);
   cout<<-km();
posted @ 2022-11-14 17:55  半步蒟蒻  阅读(44)  评论(0)    收藏  举报