二分图的最大匹配(学习笔记)

一,二分图判定

1.二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。
2.二分图无奇数圈。

3.用DFS染色判断(代码如下):

int color[maxn];
bool bipartite(int u){
	//判断结点u所在的连通分量是否为二分图
	for(int i=0;i<G[u].size();i++){
		int v=G[u][i];
		if(color[v]==color[u]) return false;
		if(!color[v]){
			color[v]=3-color[u];
			if(!bipertite(v)) return false;
		}
	}
	return true;
}

二,二分图最大匹配

1.匹配是指两两没有公共点的边集。
2.最大匹配问题:给出一个二分图,找出一个边数最大的匹配。
3.完美匹配:图中所有点都是匹配点,|X|=|Y|。
4.完美匹配一定是最大匹配,而最大匹配不一定是完美匹配。

三,增广路定理

1.从一个匹配点出发,依次经过非匹配边,匹配边,非匹配边……形成的路径叫交替路。两个端点都是未盖点->增广路(Augmenting Path,简称AP)。
2.增广路上非匹配边比匹配边数量多一,如果将匹配边改为未匹配边,反之亦然,则匹配大小会增加一且依然是交错路。
3.当找不到增广路的时候,得到最大匹配。

四,匈牙利算法

1.由增广路的性质,增广路中的匹配边总是比未匹配边多一条,所以如果我们放弃一条增广路中的匹配边,选取未匹配边作为匹配边,则匹配的数量就会增加。匈牙利算法就是在不断寻找增广路,如果找不到增广路,就说明达到了最大匹配。

2.样例程序:
(1)输入格式:
第1行3个整数,V1,V2的节点数目 ,G的边数m.
第2到m+1行,每行两个整数x,y,代表 V1中编号为x的点和V2中编号为y的点之间有边相连.
(2)输出格式:
1个整数ans,代表最大匹配数

#include 
#include 
using namespace std;
int map[105][105];
int visit[105],flag[105];
int n,m;
bool dfs(int a){
    for(int i=1; i<=n; i++){
        if(map[a][i]&&!visit[i]){
            visit[i]=1;
            if(flag[i]==0||dfs(flag[i])){
                flag[i]=a;
                return true;
            }
        }
    }
    return false;
}
int main(){
    int n1,n2;
    while(cin>>n1 >>n2 >>m){
        n=n1;
        memset(map,0,sizeof(map));
        for(int i=1; i<=m; i++){
            int x,y;
            cin>>x>>y;
            map[x][y]=1;
        }
        memset(flag,0,sizeof(flag));
        int result=0;
        for(int i=1; i<=n1; i++){
            memset(visit,0,sizeof(visit));
            if(dfs(i)) result++;
        }
        cout<<result<<endl;
    }
    return 0;
}

3.因为增广路长度为奇数,路径起始点非左即右,所以我们先考虑从左边的未匹配点找增广路。注意到因为交错路的关系,增广路上的第奇数条边都是非匹配边,第偶数条边都是匹配边,于是左到右都是非匹配边,右到左都是匹配边。

4.那么只要从起始点开始 DFS 遍历直到找到某个未匹配点,O(m),因为要枚举n个点,总复杂度为O(nm)。

五,经典例题

Uva 1663 Purifying Machine
(转自https://www.cnblogs.com/20143605–pcx/p/5140314.html)
题意:
每一个01串中最多含有一个星号,星号既可表示0也可表示1,给出一些等长的这样的01串,问最少能用多少个这样的串表示出这些串。如:000、010、0*1表示000、010、001、011,最少只需用“00星号”、“01星号”这两个即可表示出来。
分析:
如果有两个串只有一个位置上的数字不同,就可以用星号代替这个位置上的数,这样就能把两个串用一个串表示出来。因为要找最少的数目,当然星号用的越多越好,也就是说只需找出最多的对数,然后再加上不用星号表示的串的数目便是最小值。
两两判断,如果二者的二进制只有一位不同,在s1和s2之间连一条边,对应一个带星号的模板串,其中星号的位置就是s1和s2不同的那一位。

#include 
#include 
#include 
#include 
#include 
using namespace std;
 
vector<int>b;
int vis[1005];
int n,m,a[1500][1500],s[1500],t[1005];
int A[11]={1,2,4,8,16,32,64,128,256,512,1024};
 
void f(string p)//二进制转换为十进制 
{
    int r=0;
    for(int i=0;i<m;++i)
        r=r*2+p[i]-'0';
    b.push_back(r);
}
 
bool ok(int x,int y)
{
    int z=x^y;
    for(int i=0;i<11;++i)
        if(z==A[i]) return true;
    return false;
}
 
void setG()
{
    memset(a,0,sizeof(a));
    for(int i=0;i<n;++i)
        for(int j=i+1;j<n;++j)
            if(ok(b[i],b[j])) a[i][j]=a[j][i]=1;
}
 
bool match(int u)
{
    for(int v=0;v<n;++v){
        if(!a[u][v]) continue;
        if(vis[v]) continue;
        vis[v]=1;
        if(t[v]==-1||match(t[v])){
            t[v]=u;
            s[u]=v;
            return true;
        }
    }
    return false;
}
 
void solve()
{
    memset(s,-1,sizeof(s));
    memset(t,-1,sizeof(t));
    int ans=0;
    for(int i=0;i<n;++i)
        if(s[i]==-1){
            memset(vis,0,sizeof(vis));
            if(match(i)) ++ans;
        }
    ans/=2;
    for(int i=0;i<n;++i)
        if(t[i]==-1) ++ans;
    printf("%d\n",ans);
}
 
int main()
{
    string p;//n个长度为m的01串 
    while(scanf("%d%d",&m,&n)&&(n+m))
    {
        b.clear();
        for(int i=0;i<n;++i){
            cin>>p;
            int j;
            for(j=0;j<m;++j) 
			    if(p[j]=='*') break;
            if(j==m) f(p);
            else{//若有* ,则转化为其代表的两个串,分别对应 
                p[j]='0';
                f(p);
                p[j]='1';
                f(p);
            }
        }
        sort(b.begin(),b.end());
        n=unique(b.begin(),b.end())-b.begin();
        setG();
        solve();
    }
    return 0;
}

你可能感兴趣的:(图论,dfs,算法,图论)