二分图匹配算法学习笔记

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

  二分图的最大匹配:比如相亲,左边为男生队,右边为女生队,男女存在多种关系(比如男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);
            }
    }
}
原文地址:https://www.cnblogs.com/liyexin/p/11815832.html