把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷6577】【模板】二分图最大权完美匹配

点此看题面

  • 给定一张二分图,两边各有\(n\)个点,共有\(m\)条边,求这张二分图的最大权完美匹配。
  • \(n\le500,m\le\frac{n(n-1)}2\)

\(KM\)算法

这个算法的核心思想就是给每个点一个顶标(两边点都有),满足\(a_i+b_j\ge w_{i,j}\)

然后对于一条满足\(a_i+b_j=w_{i,j}\)的边,定义它为一条相等边。由相等边构成的子图称作相等子图。

\(KM\)算法建立在一个基本结论上:当每个相等子图完备匹配时,二分图得到最大匹配。

其实很容易就可以找到一个可行的顶标分配方案,因此可以找到一种顶标的分配,然后在找增广路的时候同时调整,并在发现相等子图的完备匹配时立刻匹配。

具体流程就是先分配可行顶标,然后匈牙利算法去寻找增广路,找不到的时候就调整顶标,直至找到为止。

这种做法很好写,但会被卡成\(O(n^4)\),所以需要优化。

\(DFS\)\(BFS\)的优化

本质其实不变。

就是考虑到每次调整其实变动很小,重新\(DFS\)会造成大量冗余。

因此我们改用\(BFS\),一般情况下能够做到\(O(n^3)\),但据说还是有可能被卡成\(O(n^4)\)的(该有多无聊的出题人才会卡这玩意啊)。

代码:\(O(n^3)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500
#define INF 1e9
using namespace std;
int n,m,ex[N+5],ey[N+5],a[N+5][N+5];
int s[N+5],p[N+5],vis[N+5],px[N+5],py[N+5],sl[N+5];I void Match(CI id)//BFS寻找匹配
{
	RI i,x,v,o;LL d;for(i=1;i<=n;++i) p[i]=0,sl[i]=INF;//初始化
	s[v=0]=id;W(s[v])//不断搜索
	{
		for(x=s[v],d=INF,vis[v]=i=1;i<=n;++i) !vis[i]&&//如果没有访问过
			(sl[i]>px[x]+py[i]-a[x][i]&&(sl[i]=px[x]+py[i]-a[x][i],p[i]=v),sl[i]<d&&(d=sl[o=i]));//更新,p[i]记录前驱
		for(i=0;i<=n;++i) vis[i]?(px[s[i]]-=d,py[i]+=d):sl[i]-=d;v=o;//利用最小的差值更新被访问到的点的信息
	}
	W(v) s[v]=s[p[v]],v=p[v];//更新一遍
}
int main()
{
	RI i,j;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) for(j=1;j<=n;++j) a[i][j]=-INF;//初始所有边权是-INF
	RI x,y,z;for(i=1;i<=m;++i) scanf("%d%d%d",&x,&y,&z),a[x][y]=max(a[x][y],z);//记录边信息
	for(i=1;i<=n;++i) {for(j=1;j<=n;++j) vis[j]=0;Match(i);}//KM算法
	LL t=0;for(i=1;i<=n;++i) t+=a[s[i]][i];for(printf("%lld\n",t),i=1;i<=n;++i) printf("%d ",s[i]);return 0;//输出答案并给出方案
}
posted @ 2021-04-07 21:03  TheLostWeak  阅读(116)  评论(0编辑  收藏  举报