zoukankan      html  css  js  c++  java
  • 二分图匹配总结与习题

    模板:

    (用时间戳记录可以避免每一次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.有关方格图中行与列之间的二分图匹配

     

     

  • 相关阅读:
    SPA架构的优点和缺点以及一些思考
    我们为什么要尝试前后端分离
    HTTP协议详解
    前后端分离 与 不分离
    描述一下 cookies,sessionStorage 和 localStorage 的区别
    Express中间件的意思 next()的方法
    Java笔记1Java相关概念和如何实现跨平台
    去掉EditPlus自动备份bak文件
    Java配置环境变量
    Java初学者入门应该掌握的30个概念
  • 原文地址:https://www.cnblogs.com/mowanying/p/11659878.html
Copyright © 2011-2022 走看看