二分图匹配总结与习题
Posted mowanying
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二分图匹配总结与习题相关的知识,希望对你有一定的参考价值。
模板:
(用时间戳记录可以避免每一次memset vis)
#include<bits/stdc++.h> using namespace std; #define N 2005 #define M 1000005 int match[N],vis[N],T=0;//只存一边的匹配点 int to[M],head[N],tot=0,nex[M]; void add(int a,int b) { to[++tot]=b; nex[tot]=head[a]; head[a]=tot; } bool dfs(int x) { if(vis[x]==T) return false; vis[x]=T; for(int i=head[x];i;i=nex[i]) if(!match[to[i]]||dfs(match[to[i]])) { match[to[i]]=x; match[x]=to[i]; return true; } } int main() { int n,m,e,ans=0,a,b; cin>>n>>m>>e; while(e--) { scanf("%d%d",&a,&b); if(a<=n&&b<=m) add(a,b+m),add(b+m,a); } for(int i=1;i<=m;i++) //一定是遍历m 因为存储时是加了m 说明有m长度的一边需要遍历 { T++; if(dfs(i)) ans++; } cout<<ans; }
知识:
最小点覆盖=最大匹配数,最大独立集=n-最大匹配数,最大团=补图的最大独立集。
应用:对于一些限制条件,可以抽象成选了一个,就不能选另一个的对立关系,或者是选了一个,就必须选另一个的强制关系,就可以连边转换成二分图。
练习题:
分析:
法1:二分图匹配
每个装备有两种属性,且一个装备只能选一种,对应二分图中一个点只能有一个匹配,不能匹配多个。
所以将属性作为左部点,装备作为右部点,左向右连边。
但这道题还有一个限制是:必须选择连续的属性,在二分图的模板中稍加改动即可解决这个问题:
从小到大枚举每一种属性去匹配,如果当前属性已无法匹配,后面的再怎么匹配都不会是连续的,直接break掉。
为什么二分图能解决这样的问题呢?
可以考虑匈牙利算法的具体过程:在匹配值为 i的技能时,那么 1到 i−1的属性肯定已经匹配完成,所以如果 i对应的编号 j被匹配了的话,那么就让匹配 j
的那个属性 p 再去找别的物品标号匹配,形象地说,就是用别的物品来释放攻击力为 p
的这个技能,用 j这个物品释放攻击力为 i的技能。如果找到这样一条增广路,那么就说明当前可以匹配,ans++。(证明来源)
法2:并查集
直接对属性建边(边其实对应的是选一个装置),建出来的是多个块,这些块里,若边数>=点数,这里面的点都可以被覆盖。
若<点数,这个块又是联通的,就一定是一棵树,我们就必须舍弃一个点不选(由贪心知舍弃最大的点最优)。
用并查集维护联通性,并记录一个块中的点数,以及其是否存在一个环(若存在,即边数>=点数)。
从小到大选取点,如果所在的块有环,就直接选,否则将所在块的大小减小(相当于删除了一条边来获取这个点)。当一个点所在的块大小为1,则说明没有边可选,直接break即可。
把模式看做点,即选择最少的点覆盖所有的任务,求最小点覆盖即可。
//#include<bits/stdc++.h> #include <iostream> #include <cstdio> #include <cstdlib> #include <cmath> #include <algorithm> #include <cstring> #include <stack> #include <cctype> #include <queue> #include <string> #include <vector> #include <set> #include <map> #include <climits> using namespace std; #define N 2005 #define ri register int int n,m,k,ans,Ti,vis[N],match[N]; vector<int> e[N]; bool dfs(int x) { if(vis[x]==Ti) return false; vis[x]=Ti; for(ri i=0;i<e[x].size();++i){ int v=e[x][i]; if(!match[v] || dfs(match[v])){ match[v]=x; match[x]=v; return true; } } return false; } void init() { for(ri i=0;i<=n+m;++i) e[i].clear(),vis[i]=0,match[i]=0; Ti=0; ans=0; } int main() { while(1){ scanf("%d",&n); if(n==0) break; scanf("%d%d",&m,&k); init(); int a,b,c; for(ri i=1;i<=k;++i){ scanf("%d%d%d",&a,&b,&c); if(b==0||c==0) continue;//注意细节!!! e[b].push_back(c+n); e[c+n].push_back(b); } //memset(vis,0,sizeof(vis))用时间戳可以减少每次memset的复杂度 for(ri i=1;i<=n;++i) Ti++,ans+=dfs(i); printf("%d ",ans); } }
以上是关于二分图匹配总结与习题的主要内容,如果未能解决你的问题,请参考以下文章