[TOC]php
###知识点:二分图最大权匹配c++
###知识点概要: 二分图最大权匹配是指在二分图中,每一条边都会有一个权值,询问的是匹配以后的最大权值而不是最大的匹配数,主要的作法有KM和最费用流的作法。git
###知识点详解: 二分图最大权匹配的主要作法是KM和费用流。而费用流的作法是比较简单的,新建一个源点和一个汇点,而后从源点向每个左集合中的点连一条流量1,费用为0的边,从右集合中的点向汇点连一条流量为1,费用为0的边,最后再将原图中的边连起来,设置成流量为1,费用为权值的边,这样跑一边最小费用最大流以后,获得的就是二分图的最大权匹配。 接下来开始讲KM的作法。KM的作法的核心点在于对于二分图左右两个集合中的每个点分别记一个标杆数组$X[i]$,$Y[i]$,而且在算法执行的仍以时刻,咱们都要保持$X[i]+Y[j]>=w(i,j)$这个性质。在理解KM算法以前,咱们须要具体的理解这个标杆数组在KM算法中的做用。首先咱们须要知道KM算法的正确性基于这个定理: 若二分图中由全部$X[i]+Y[j]=w(i,j)$的边$(i,j)$组成的导出子图具备完备匹配,那么这个完备匹配就是原来二分图中的最大权匹配。 这为何是正确的呢?咱们以前在定义标杆数组的时候,就保证了$X[i]+Y[j]>=w(i,j)$这个性质在仍以时刻都会成立。而咱们导出的子图因为由完备匹配,因此这个完备匹配的权值必定就是全部标杆数组的和,而又根据$X[i]+Y[j]>=w(i,j)$,因此咱们在原图中任意其余的匹配方案,都没法比这个匹配方案更加优秀。因此咱们的导出子图的完备匹配的权值就是原图的最大权匹配。 因而咱们接下来就须要求这个完备匹配了。咱们先初始化标杆数组$X$为与这个点相连的边的最大权值,初始$Y$为0,这样咱们就先保证了在初始状况下知足咱们的标杆数组的性质。可是如今的问题在于,对于如今的标杆数组$X$和$Y$,咱们的导出子图并不必定是存在完备匹配的,因而咱们就须要调整这个标杆数组使得这个导出子图可以包括原图中更多的边,来让它具备完备匹配。而且咱们须要这个保证这个调整的量要足够小,让它不会增长一些没必要要的边致使答案变劣。因而咱们能够这样进行调整:咱们经过调整以前的最后一次$Dfs$交错路,找到全部$i$被访问过的,可是$j$并无被访问到的边$(i,j)$,记$d$为$X[i]+Y[j]-w(i,j)$的最小值,而后把每个左集合被访问的点的标杆$X[i]$减去$d$,每个右集合被访问的点的标杆$Y[i]$加上$d$。而后继续求完备匹配。为何这样的方法是正确的呢,咱们这样分析: 1.对于已经在导出子图中的边$(i,j)$,因为$X[i]$减小了$d$,$Y[i]$增长了$d$,因此这个标杆和仍是不变的,仍然在这个导出子图中。 2.对于不在导出子图的边$(i,j)$,若是$i$被访问过了,$j$没有被访问过,那么这个标杆和$X[i]+Y[j]$会变小,那么这个标杆和就有可能等于$w(i,j)$,因此这条边就会有可能加入这个导出子图。 3.对于不在导出子图的边$(i,j)$,若是$i$没有被访问过,$j$被访问过了,那么这个标杆和$X[i]+Y[j]$就会变大,那么这条边仍然不能加入导出子图。 这样咱们就能够证实这种修改的方式是正确的了。因此咱们如今就能够得出二分图最大权匹配的步骤了: 1.初始化标杆数组 2.枚举每个点进行二分图的匹配 3.若是这个点没有匹配到,那么修改标杆数组继续进行匹配 4.若是这个点匹配到了,那么继续匹配下一个点 最后把全部的匹配边的权值加起来就是最大权匹配的答案了。 可是咱们分析一下,枚举每个点,须要$O(n)$的复杂度,求出标杆数组的修改量,须要$O(n^2)$的复杂度,每一个点最多修改$O(n)$次,因此总复杂度为$O(n^4)$。而后咱们观察咱们这些步骤,发现咱们复杂度的瓶颈实际是在求标杆数组的修改量上的。由于枚举点是不可避免的,而后每一个点的修改也是指望的,因此咱们只能对求修改量的方法进行优化。朴素的方法是枚举每个访问的$i$和未访问的$j$,而后咱们能够对此进行优化,记$slack$数组为松弛变量,每次访问到一个$i$的时候,就把与这个点相连的点$j$,而且当前没法加入导出子图的点,用$X[i]+Y[j]-w(i,j)$来更新$slack[j]$。而后咱们只须要枚举每个右集合中未访问到的点$v$,而后用$slack[v]$更新修改量$d$,这样就能够作到求修改量的复杂度为$O(n)$的了,而后总复杂度就能够优化成$O(n^3)$的了。不过还要注意的是,修改右集合中的标杆$Y[i]$的时候,也要同时修改$slack[i]$。算法
###模板 HDU2255 奔小康赚大钱 题目传送门数组
####Code优化
#pragma GCC optimize (3,"inline","Ofast") #include <bits/stdc++.h> using namespace std; typedef long long ll; bool Finish_read; template<class T>inline void read(T &x){Finish_read=0;x=0;int f=1;char ch=getchar();while(!isdigit(ch)){if(ch=='-')f=-1;if(ch==EOF)return;ch=getchar();}while(isdigit(ch))x=x*10+ch-'0',ch=getchar();x*=f;Finish_read=1;} template<class T>inline void print(T x){if(x/10!=0)print(x/10);putchar(x%10+'0');} template<class T>inline void writeln(T x){if(x<0)putchar('-');x=abs(x);print(x);putchar('\n');} template<class T>inline void write(T x){if(x<0)putchar('-');x=abs(x);print(x);} /*================Header Template==============*/ const int N=505; const int inf=2e9+7; int n; int wx[N],wy[N],weight[N][N],slack[N]; int belong[N],visx[N],visy[N]; /*==================Define Area================*/ int FindPath(int u) { visx[u]=1; for(int v=1;v<=n;v++) { if(visy[v]) continue; int t=wx[u]+wy[v]-weight[u][v]; if(!t) { visy[v]=1; if(belong[v]==-1||FindPath(belong[v])) { belong[v]=u; return 1; } } else if(slack[v]>t) slack[v]=t; } return 0; } int Km() { for(int i=1;i<=n;i++) wx[i]=-inf; for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { wx[i]=max(wx[i],weight[i][j]); } } for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { slack[j]=inf; } while(1) { memset(visx,0,sizeof visx); memset(visy,0,sizeof visy); if(FindPath(i)) break; int ret=inf; for(int j=1;j<=n;j++) { if(!visy[j]&&ret>slack[j]) ret=slack[j]; } for(int j=1;j<=n;j++) { if(visx[j]) wx[j]-=ret; } for(int j=1;j<=n;j++) { if(visy[j]) wy[j]+=ret; else slack[j]-=ret; } } } int ans=0; for(int i=1;i<=n;i++) { if(~belong[i]) ans+=weight[belong[i]][i]; } return ans; } int main() { while(scanf("%d",&n)!=EOF) { memset(belong,-1,sizeof belong); for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { read(weight[i][j]); } } printf("%d\n",Km()); } return 0; }