二分图匹配总结与习题

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;
}
View Code

知识:

最小点覆盖=最大匹配数,最大独立集=n-最大匹配数,最大团=补图的最大独立集。

应用:对于一些限制条件,可以抽象成选了一个,就不能选另一个的对立关系,或者是选了一个,就必须选另一个的强制关系,就可以连边转换成二分图。

练习题:

1.P1640 [SCOI2010]连续攻击游戏

分析:

法1:二分图匹配

每个装备有两种属性,且一个装备只能选一种,对应二分图中一个点只能有一个匹配,不能匹配多个。

所以将属性作为左部点,装备作为右部点,左向右连边

但这道题还有一个限制是:必须选择连续的属性,在二分图的模板中稍加改动即可解决这个问题:

从小到大枚举每一种属性去匹配,如果当前属性已无法匹配,后面的再怎么匹配都不会是连续的,直接break掉。

为什么二分图能解决这样的问题呢?

可以考虑匈牙利算法的具体过程:在匹配值为 i的技能时,那么 1i−1的属性肯定已经匹配完成,所以如果 i对应的编号 j被匹配了的话,那么就让匹配 j

的那个属性 p 再去找别的物品标号匹配,形象地说,就是用别的物品来释放攻击力为 p

的这个技能,用 j这个物品释放攻击力为 i的技能。如果找到这样一条增广路,那么就说明当前可以匹配,ans++。(证明来源

法2:并查集

直接对属性建边(边其实对应的是选一个装置),建出来的是多个块,这些块里,若边数>=点数,这里面的点都可以被覆盖。

若<点数,这个块又是联通的,就一定是一棵树,我们就必须舍弃一个点不选(由贪心知舍弃最大的点最优)。

用并查集维护联通性,并记录一个块中的点数,以及其是否存在一个环(若存在,即边数>=点数)。

从小到大选取点,如果所在的块有环,就直接选,否则将所在块的大小减小(相当于删除了一条边来获取这个点)。当一个点所在的块大小为1,则说明没有边可选,直接break即可。

2.poj 1325:Machine Schedule

把模式看做点,即选择最少的点覆盖所有的任务,求最小点覆盖即可。

 

技术图片
//#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);
    }
}
View Code

3.有关方格图中行与列之间的二分图匹配

 

 

 

 

以上是关于二分图匹配总结与习题的主要内容,如果未能解决你的问题,请参考以下文章

二分匹配总结

网络流常见建图套路总结(重制版)

转二分图的最大匹配

知识点简单总结——带花树(一般图最大匹配)

巨坑网络流线性规划与网络流24题

二分图——匈牙利算法简述