zoukankan      html  css  js  c++  java
  • 二分图匹配算法学习笔记

      我用自己的语言解释一下何为二分图:图中所有点分成两部分,就像站队,左边一队,右边一队。同一队两点之间没有关系,不同队的可能存在多种关系。

      二分图的最大匹配:比如相亲,左边为男生队,右边为女生队,男女存在多种关系(比如男1喜欢女2和女3),男男之间当然没有关系了。最大匹配就是这么个情况,尽量成全更多的男女成为一对,最多的对数,就是最大匹配数。

      匈牙利算法:关键点是找增广路,是这么个情况:男1喜欢女1,但是女1已经和男4好上了,男1很执着,女1思想也不太坚定,于是女1问男4,他可不可以换个女生。如果男4成功换了女伴,那么理所当然男1成功和女1成为情侣!这就是找到了一条增广路

      下面拿板子题来说明代码:  HDU 2063 

     题意是中文就不多做解释了

      

    #include<iostream>   
    #include<cstdio>    
    #include<cstring>
    const int maxn=505;
    int e[maxn][maxn];
    int book[maxn];
    int match[maxn];
    int  k,n,m;
    using namespace std;
    int dfs(int x)
    {
        for(int i=1;i<=m;i++)
        {
            if(!book[i]&&e[x][i])  //关于book数组,我这么解释:1:第2层if没有发生,即男x无法和i配对,说明女i已经有人了,而且女i的男友无法再找到新女生,此时有必要把book[i]=1,因为这个女人碰不得啊,以后大家就不要再找她了。
                       //2:第二层if 发生了,女i被标记,因为女i的男友想再找其他女生,就不能再找女i了。 { book[i]
    =1; if(!match[i]||dfs(match[i]))//女i没有男友或者男友可以换个女生,那么x可以和i配对
    { match[i]
    =x; return 1; } } } return 0; } int main() { while(scanf("%d",&k)) { if(k==0) break; int ans=0; scanf("%d%d",&n,&m); memset(match,0,sizeof(match)); memset(e,0,sizeof(e)); int a,b; for(int i=1;i<=k;i++) { scanf("%d%d",&a,&b); e[a][b]=1; } for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++)    //每次清空book {   book[j]=0; } if(dfs(i))   // ans++; } cout<<ans<<endl; } return 0; }

        二分图最大独立集:实质是个点集,这个集合里的点无论怎样都两两相连不到一起,满足这个要求的点数最多的一个。也就是集合里任意两点之间无关系。用最大匹配求出最大独立集,

        最大独立集 = 顶点数 - 最大匹配数

        HDU1068      

        题意:n个同学,一些男女同学会有缘分成为情侣,格式ni:(m) n1 n2 n3表示同学ni有缘与n1,n2,n3成为情侣,求集合中不存在有缘成为情侣的同学的最大同学数

        模板,但是要注意,这并不是两个集合,所以最大匹配相当于求了两遍,最大匹配 / 2;

        还有,输入格式注意一下,第一次这么搞,没想到scanf还有这种操作。

    #include<iostream>
    #include<cstring>
    using namespace std;
    const int maxn=1001;
    int e[maxn][maxn];
    int match[maxn];
    int book[maxn];
    int n;
    int sum=0;
    void init()
    {
        memset(e,0,sizeof(e));
        int st,m;
        sum=0;
        memset(match,-1,sizeof(match));
        for(int i=0;i<n;i++)
        {
            scanf("%d: (%d)",&st,&m);
            int to;
            while(m--)
            {
                scanf("%d",&to);
                e[st][to]=1;
            }
        }
    }
    int dfs(int x)
    {
        for(int i=0;i<n;i++)
        {
            if(book[i]==0&&e[x][i]==1)
            {
                book[i]=1;
                if(match[i]==-1||dfs(match[i]))
                {
                    match[i]=x;
                    return 1;
                }
            }
        }
        return 0;
    }
    int main()
    {
        while(~scanf("%d",&n))
        {
            init();
            for(int i=0;i<n;i++)
            {
                for(int j=0;j<n;j++)
                    book[j]=0;
                if(dfs(i))
                    sum++;
            }
            printf("%d
    ",n-sum/2);
        }
        return 0;
    }

      下面放个很有意思的题  HDU1281

      中文题意,为了防止车子互相攻击,那么车在某一行+某一列只有一个。相当于行与列的匹配,由于存在不能放车的点,用e[][]来存坐标来进行判断能否放车,其实就和之前的一样,e[i][j]为第i行和

    第j列没有关系,仅此而已。所谓的重要点,就是去掉它以后,求出的最大匹配数比原来的少,求这些点。那么我们枚举每一个e[][]==1大的点,去掉它,跑一下匈牙利,求出的最大匹配和原来的比较。计数。

      

     1 #include<iostream>
     2 #include<cstring>
     3 #include<cstdio>
     4 using namespace std;
     5 const int maxn=105;
     6 int e[maxn][maxn];
     7 int match[maxn];
     8 int book[maxn];
     9 int n,m,k;
    10 int all=0;
    11 void init()
    12 {
    13     memset(e,0,sizeof(e));
    14     all=0;
    15 }
    16 int dfs(int x)
    17 {
    18     for(int i=1;i<=m;i++)
    19     {
    20         if(!book[i]&&e[x][i])
    21         {
    22             book[i]=1;
    23             if(!match[i]||dfs(match[i]))
    24             {
    25                 match[i]=x;
    26                 return 1;
    27             }
    28         }
    29     }
    30     return 0;
    31 }
    32 int xyl()
    33 {
    34     int ans=0;
    35     memset(match,0,sizeof(match));  //注意,每次跑匈牙利都要初始化match!
    36     for(int i=1;i<=n;i++)
    37     {
    38         memset(book,0,sizeof(book));
    39         if(dfs(i))
    40             ans++;
    41     }
    42     return ans;
    43 }
    44 int main()
    45 {
    46     int ww=1;
    47     while(~scanf("%d%d%d",&n,&m,&k))
    48     {
    49         init();
    50         int x,y;
    51         int cnt=0;
    52         for(int i=1;i<=k;i++)
    53         {
    54             scanf("%d%d",&x,&y);
    55             e[x][y]=1;
    56         }
    57         all=xyl();
    58         for(int i=1;i<=n;i++)
    59         {
    60             for(int j=1;j<=m;j++)
    61                 {
    62                     if(e[i][j])
    63                     {
    64                         e[i][j]=0;
    65                         int ans=xyl();
    66                     //    cout<<ans<<endl;
    67                         if(ans<all)
    68                             cnt++;
    69                         e[i][j]=1;
    70                     }
    71                 }
    72         }
    73         printf("Board %d have %d important blanks for %d chessmen.
    ",ww++,cnt,all);
    74     }
    75 }

        二分图的染色法判断: 1. 二分图当且仅当不含奇数环。(手推一下,会出现矛盾,所以不能含有奇数环),不含奇数环一定是二分图。

        具体参见ACWING 图论第三讲。对每一个点进行染色,比如某点染1色,那么其相邻点染2色,如果在染的过程中,出现了矛盾,比如i 点本来改染成1色,但是它的当前配色为2,那肯定不是个二分图。ACWING 基本板子题:这里用邻接表来存图

        

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int maxn=2e5+10;
    int n,m;
    int h[maxn],e[maxn],ne[maxn];  //对e,ne要开大点,我也不知道为撒
    int color[maxn];
    int ok=0;
    int idx;
    void add(int a,int b)
    {
        e[idx]=b;
        ne[idx]=h[a];
        h[a]=idx++;
    }
    bool dfs(int u,int c)
    {
        color[u]=c;
        for(int i=h[u];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(!color[j])
            {
                if(!dfs(j,3-c))
                {
                    return false;
                }
            }
            else if(color[j]==c)
                {
                    return false;
                }
        }
        return true;
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        memset(h,-1,sizeof(h));
        for(int i=1 ;i<=m ;i++)
        {
             int a,b ;
             scanf("%d%d",&a ,&b );
             add(a,b);
             add(b,a);    
        }    
        for(int i=1  ; i <= n; i++)
        {
            if(!color[i])    
            {
                if(!dfs(i,1))
                {    
                
                    ok=1;  //出现矛盾
                    break;
                }
            }
        }
        if(ok)    
            cout<<"No"<<endl;
        else
            cout<<"Yes"<<endl;
    }

             

       HDU 5285 

        原题自行打开看吧,我解释一下题意:就是n个人,分成两组,m个不认识关系。把认识的人分一组,分两组。比如输入1 2,就表明1与2不认识,不认识就不能分到同一组。

        很明显,判断二分图,不是二分图就输出Poor wyh。是的话先输出大的一组,然后是较小的一组。套基本二分图染色模板,但是要注意,题没这么简单,因为题目中有多个连通块,就是有多个二分图,其次还存在单点。所以呢,运用贪心的思想,分别把单个的二分图求出来,然后用ans加上较小的那一组的人数,这样单点就自动记入较大的一组,n减ans就可以了。因为不同的二分图:比如A组和B组,A组的人肯定和B组不存在不认识关系,所以他们可以加入同一组。很多题解加入了vis等好多不易理解且繁琐的东西,我依据二分图染色模板,改造出了易于小白理解的代码,与原模板没什么两样。

        

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    const int maxn=2e5+10;
    int h[maxn],e[maxn],ne[maxn];  //邻接表存图
    int num[3];
    int  color[maxn];
    bool vis[maxn];
    int idx;
    int ok=0;
    void init()
    {
        idx=0;ok=0;
        memset(h,-1,sizeof(h));
        memset(e,0,sizeof(e));
        memset(ne,0,sizeof(ne));
    //    memset(vis,false,sizeof(vis));
        memset(color,0,sizeof(color));
    }
    
    void add(int a,int b){
        e[idx]=b;
        ne[idx]=h[a];
        h[a]=idx++;
    }
    bool dfs(int u,int c)
    {
    //    vis[u]=true;
        color[u]=c;
        num[c]++;  //计数
        for(int i=h[u];i!=-1;i=ne[i])
        {
            int j=e[i];
             if(!color[j])
             {
                 if(!dfs(j,3-c))
                 return false;
             }
             else if(color[j]==c)
                 return false;
                 
        }
        return true;
    }
    int main()
    {
        int t;
        scanf("%d",&t);
        while(t--)
        {
            init();
            int n,m;
            scanf("%d%d",&n,&m);
            int a,b;
            int mid=m;
            while(mid--)
            {
                scanf("%d%d",&a,&b);
                add(a,b);add(b,a);
            }    
        //    cout<<m<<endl;
            
            if(n<=1)    //坑点,记得特判!
            {
                printf("Poor wyh
    ");
                continue;
            }    
            if(m==0)
            {
                printf("%d 1
    ",n-1);
                continue;
            }    
            int ans=0;
            for(int i=1;i<=n;i++)
            {
                if(!color[i])
                {
                    num[1]=0;num[2]=0;  //新的二分图,那么计数器就要清一下
                    if(!dfs(i,1))
                    {
                        ok=1;break;
                    }
                    ans+=min(num[1],num[2]);    
                }
            }
            if(ok)    
                printf("Poor wyh
    ");
            else
                {
                    printf("%d %d
    ",n-ans,ans);
                }
        }
    }
  • 相关阅读:
    对于数据库表排他更新的理解
    小学数学题
    胜败兵家事不期
    ORACLE外键约束(FORIGEN KEY)
    spring客户端jsp与服务端交互方法
    nodejs学习笔记package.json
    关于viewport
    dl,dt,dd的用法
    304-NOT MODIFIED
    $(document).ready()和 window.onload的区别
  • 原文地址:https://www.cnblogs.com/liyexin/p/11815832.html
Copyright © 2011-2022 走看看