二分图知识点温习
二分图
定义:顾名思义就是将整个图分为两个部分的图。这里我们将点分成两个集合,A,B并且规定,任意同一个集合里的点没有直接相连的边,也就是说所有的点都是从A集合里的一个点连向B集合里的一个点。这样的图叫做二分图。
判定:那么如何判定一个图是不是二分图呢?先给出定理:当一个图为二分图是,当且仅当这个图中没有奇环(点的个数为奇数的环).对于这个定理的证明,不详解释了。(其实是因为我不懂....)但我们可以从构造的角度了解这个定理。二分图不是要求两个点集吗,那我们就尝试将一个图给划分成一个二分图。首先我们可以先对非环上的点进行划分,他们最多是一个数,我们定义一个点权为1和2,分别代表两个集合。依次将所有的点给成1和2,也就是将所有1的点邻近节点给成2,这样因为没有环的存在,一定是合法的。接下来考虑环的情况,注意我们这里只考虑最简单环的情况(即纯粹是一个环不含其余多余的节点),若环的长度为偶数,我们发现这样染色并没有什么冲突,但若环的长度为奇数,那么一定会有冲突,这也就是二分图与奇环格格不入的原因了。
最大匹配:对于一个二分图中的一个边集,若任意两条边都没有公共点的话,则成为这是二分图的一组匹配,边的数量最多的一组匹配被称为最大匹配。
inline bool dfs(int x) { for(register int i=link[x];i;i=a[i].next) { int y=a[i].y; if(vis[y]!=id) { vis[y]=id; if(!match[y]||dfs(match[y])) { match[y]=x;return true; } } } return false; } for(int i=1;i<=n;++i) { id++; if(dfs(i)) ans++; }
这里对于右部节点只能便利一次的理解:首先我们要明确我们加入匹配的点不会因为我们将其换点而失去匹配,也就是说我们每次找新的匹配都是将未加入匹配的点加入其中,若某次右边的点访问过,若其左部匹配的点同意更换则直接结束,若不同意则说明其左部匹配的点在未加入匹配的点钟找不到新的匹配,所以在当前这个点(代码中的i)找新匹配时其左部匹配的点不同意,则会一直都不同意。这样理解即可。
最大带权匹配 :若二分图中的边带有权值,要求找到最大匹配的同时要求所有边的全职和也最大。
KM算法是个非常有优秀的算法,对于稠密图的情况,复杂度比网络流更优秀。
//右边点的匹配点.左边点,右边点是否使用.时间戳,左边点,右边点的顶标. inline bool find(int x) { usex[x]=id;//x已使用 for(int i=1;i<=n;++i)//枚举每一个右边的点. { if(usey[i]!=id&&cx[x]+cy[i]==w[x][i])//如果右边点未使用并且符合期望的规则. { usey[i]=id; //右边点i已使用. if(!match[i]||find(match[i])) {match[i]=x;return true;} }//若i未匹配或者i的匹配点能换点。 } return false; } inline int KM() { for(int i=1;i<=n;++i)//枚举每个左边的点。 { while(1) { id++; //更新时间戳。 int d=qwq;//方便更新需要改的最小期望。 if(find(i)) break; //如果能为i找到匹配,则不需要改期望,直接找下一个点。 for(int j=1;j<=n;++j) if(usex[j]==id) //否则,找到所有使用过的左边点 for(int k=1;k<=n;++k) if(usey[k]!=id)//所有未被用的右边点 d=min(d,cx[j]+cy[k]-w[j][k]); //更新需要改的最小期望. for(int j=1;j<=n;++j) if(usex[j]==id) cx[j]-=d; //更新左边的顶标 for(int j=1;j<=n;++j) if(usey[j]==id) cy[j]+=d; //更新右边的顶标. } } int ans=0; for(int i=1;i<=n;++i) ans+=w[match[i]][i];//累加答案. return ans; }