二分图

二分图的相关概念

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

1.匹配:给定一个二分图,在G的一个子图G’中,如果G’的边集中的任意两条边都不依附于同一个点,则称G’的边集为G的一个匹配

最大匹配:在所有的匹配中,边数最多的那个匹配就是二分图的最大匹配了

1'.多重匹配:在匹配的基础上,允许多对一。即一个集合Y的元素可以对应多个集合X的元素,但一个集合X的元素只能对应一个集合Y中的元素

一般情况求可以构成多重匹配的最小限制值limit,对limit进行二分

2.点覆盖:在点集合中,选取一部分点,这些点能够把所有的边都覆盖了。这些点就是点覆盖集

最小点覆盖:在所有的点覆盖中,点数最小的那个叫最小点覆盖

3.边覆盖:在边集合中,选取一部分边,这些边能够把所有的点都覆盖了。这些边就是边覆盖集

最小边覆盖:在所有的边覆盖中,边数最小的那个叫最小边覆盖

4.独立集:在所有的点中选取一些点,这些点两两之间没有连线,这些点就叫独立集(即任意两点都不相连的点的集合)

最大独立集:独立集中,点数最多的那个集合

5.完全子图(完全子图好像也叫团):任意两点都相连的点的集合

最大完全子图:完全子图中,点数最多的那个集合

6.对于有向无环图(Directed Acyclic Graph),有路径覆盖的概念:

路径覆盖:在图中找一些路径,这些路径覆盖图中所有的点,每个点都只与一条路径相关联

最小路径覆盖:在所有的路径覆盖中,路径个数最小的就是最小路径覆盖了

7.最大权匹配(最佳匹配):当给二分图中的边加上权之后,边权最大的匹配

好像这个最大权匹配一定是完备匹配(包含个数少的那个集合的所有点,前提是这些点都有边相连)

8.最小点权覆盖:当给点加上权之后,点权最小的点覆盖

可以转换为最大权匹配。把边权设为点权的和(求所有点的权的和的话)、大的或者小的(只要求选其中一个点的话)

关系:
1.最大匹配数:匈牙利或者网络流

2.最小点覆盖数=最大匹配数(证明?König定理是一个二分图中很重要的定理,它的意思是,一个二分图中的最大匹配数等于这个图中的最小点覆盖数。)

3.最小边覆盖数=点数-最大匹配数(想一下,要想最少的边,那就要匹配最多的点,因为匹配了的边 一条边可以连接两个未连接点,另外 加上未匹配的点数即可,因为这些点每个都需要一条边连接,所以总共需要的边数 就为 最大匹配数 + 点数 - 2*最大匹配数)

4.最大独立数=点数-最大匹配数

5.最大完全数=原图的补图的最大独立数

6.DAG的最小路径覆盖数=DAG图中的点数-相应二分图中的最大匹配数(证明?)

建图:先拆点,将每个点分为两个点,左边是1到n个点,右边是1到n个点,然后每一条有向边对应左边的点指向右边的点

7.最大权匹配(最佳匹配):KM算法,好像也能用网络流

8.最小点权覆盖=最小权匹配

相关博客:http://dsqiu.iteye.com/blog/1689505

下面以题目来看模板的使用:

HDU - 2063 过山车(最大匹配数)(模板)

d.男生女生一起坐过山车,每一排有两个座位,但是有个条件,就是每个女生必须找个男生做同伴一起(但是女生只愿意和某几个男生中的一个做同伴),求最多可以有多少对男女生组合坐上过山车。

s.二分图的最大匹配,女生作为X集合(左边),男生作为Y集合(右边)

c.匈牙利算法(邻接矩阵):

/*
顶点编号从0开始的
邻接矩阵(匈牙利算法)
二分图匹配(匈牙利算法的DFS实现)(邻接矩阵形式)
初始化:g[][]两边顶点的划分情况
建立g[i][j]表示i->j的有向边就可以了,是左边向右边的匹配
g没有边相连则初始化为0
uN是匹配左边的顶点数,vN是匹配右边的顶点数
左边是X集,右边是Y集
调用:res=hungary();输出最大匹配数
优点:适用于稠密图,DFS找增广路,实现简洁易于理解
时间复杂度:O(VE)
*/
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

const int MAXN=512;
int uN,vN;//u,v 的数目,使用前面必须赋值
int g[MAXN][MAXN];//邻接矩阵,记得初始化
int linker[MAXN];//linker[v]=u,表示v(右边Y集合中的点)连接到u(左边X集合中的点)
bool used[MAXN];
bool dfs(int u){//判断以X集合中的节点u为起点的增广路径是否存在
    for(int v=0;v<vN;v++)//枚举右边Y集合中的点
        if(g[u][v]&&!used[v]){//搜索Y集合中所有与u相连的未访问点v
            used[v]=true;//访问节点v
            if(linker[v]==-1||dfs(linker[v])){//是否存在增广路径
                //若v是未盖点(linker[v]==-1表示没有与v相连的点,即v是未盖点),找到增广路径
                //或者存在从与v相连的匹配点linker[v]出发的增广路径
                linker[v]=u;//设定(u,v)为匹配边,v连接到u
                return true;//返回找到增广路径
            }
        }
        return false;
}
int hungary(){//返回最大匹配数(即最多的匹配边的条数)
    int res=0;//最大匹配数
    memset(linker,-1,sizeof(linker));//匹配边集初始化为空
    for(int u=0;u<uN;u++){//找X集合中的点的增广路
        memset(used,false,sizeof(used));//设Y集合中的所有节点的未访问标志
        if(dfs(u))res++;//找到增广路,匹配数(即匹配边的条数)+1
    }
    return res;
}

int main(){
    int i,ans;
    int K,M,N;//K边数,M是左边的顶点数,N是右边的顶点数
    int u,v;
    while(~scanf("%d",&K)){
        if(K==0)break;
        scanf("%d%d",&M,&N);
        uN=M;//匹配左边的顶点数
        vN=N;//匹配右边的顶点数
        memset(g,0,sizeof(g));//二分图的邻接矩阵初始化
        for(i=0;i<K;++i){
            scanf("%d%d",&u,&v);
            g[--u][--v]=1;//顶点编号从0开始的
        }
        ans=hungary();
        printf("%d
",ans);
    }
    return 0;
}
View Code

c2.匈牙利算法(邻接表):

/*
//顶点编号从0开始的
匈牙利算法邻接表形式
使用前用init()进行初始化,给uN赋值
加边使用函数addedge(u,v)
*/
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

const int MAXN=512;//点数的最大值
const int MAXM=1024;//边数的最大值
struct Edge{
    int to,next;
}edge[MAXM];
int head[MAXN],tot;
void init(){
    tot=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v){
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int linker[MAXN];
bool used[MAXN];
int uN;
bool dfs(int u){
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(!used[v]){
            used[v]=true;
            if(linker[v]==-1||dfs(linker[v])){
                linker[v]=u;
                return true;
            }
        }
    }
    return false;
}
int hungary(){
    int res=0;
    memset(linker,-1,sizeof(linker));
    //点的编号0~uN-1
    for(int u=0;u<uN;u++){
        memset(used,false,sizeof(used));
        if(dfs(u))res++;
    }
    return res;
}
int main(){
    int i,ans;
    int K,M,N;
    int u,v;
    while(~scanf("%d",&K)){
        if(K==0)break;
        scanf("%d%d",&M,&N);
        uN=M;//匹配左边的顶点数
        init();//初始化
        for(i=0;i<K;++i){
            scanf("%d%d",&u,&v);
            addedge(--u,--v);//顶点编号从0开始的
        }
        ans=hungary();
        printf("%d
",ans);
    }
    return 0;
}
View Code

c3.匈牙利算法(邻接表,用vector实现):

/*
//顶点编号从1开始的
用STL中的vector建立邻接表实现匈牙利算法
效率比较高
处理点比较多的效率很高。1500的点都没有问题
*/
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;

const int MAXN=512;//这个值要超过两边个数的较大者,因为有linker
int linker[MAXN];
bool used[MAXN];
vector<int>G[MAXN];
int uN;
bool dfs(int u)
{
    int sz=G[u].size();
    for(int i=0; i<sz; i++)
    {
        if(!used[G[u][i]])
        {
            used[G[u][i]]=true;
            if(linker[G[u][i]]==-1||dfs(linker[G[u][i]]))
            {
                linker[G[u][i]]=u;
                return true;
            }
        }
    }
    return false;
}

int hungary()
{
    int u;
    int res=0;
    memset(linker,-1,sizeof(linker));
    for(u=1; u<=uN; u++)
    {
        memset(used,false,sizeof(used));
        if(dfs(u)) res++;
    }
    return res;
}

int main()
{
    int i,ans;
    int K,M,N;
    int u,v;
    while(~scanf("%d",&K))
    {
        if(K==0)break;
        scanf("%d%d",&M,&N);
        uN=M;//匹配左边的顶点数
        for(i=0; i<MAXN; i++)//二分图的邻接表初始化
            G[i].clear();
        for(i=0; i<K; ++i)
        {
            scanf("%d%d",&u,&v);
            G[u].push_back(v);//顶点编号从1开始的
        }
        ans=hungary();
        printf("%d
",ans);
    }
    return 0;
}
View Code

c4.Hopcroft-Karp算法(邻接矩阵):

/* *********************************************
//顶点编号从1开始的
二分图匹配(Hopcroft-Carp的算法)。
初始化:g[][]邻接矩阵
调用:res=MaxMatch();  Nx,Ny要初始化!!!
时间复杂大为 O(V^0.5 E)
适用于数据较大的二分匹配
需要queue头文件
********************************************** */
#include<iostream>
#include<stdio.h>
#include<queue>
#include<string.h>
using namespace std;
const int MAXN=512;
const int INF=1<<28;
int g[MAXN][MAXN],Mx[MAXN],My[MAXN],Nx,Ny;
int dx[MAXN],dy[MAXN],dis;
bool vst[MAXN];
bool searchP()
{
    queue<int>Q;
    dis=INF;
    memset(dx,-1,sizeof(dx));
    memset(dy,-1,sizeof(dy));
    for(int i=1; i<=Nx; i++)
        if(Mx[i]==-1)
        {
            Q.push(i);
            dx[i]=0;
        }
    while(!Q.empty())
    {
        int u=Q.front();
        Q.pop();
        if(dx[u]>dis)  break;
        for(int v=1; v<=Ny; v++)
            if(g[u][v]&&dy[v]==-1)
            {
                dy[v]=dx[u]+1;
                if(My[v]==-1)  dis=dy[v];
                else
                {
                    dx[My[v]]=dy[v]+1;
                    Q.push(My[v]);
                }
            }
    }
    return dis!=INF;
}
bool DFS(int u)
{
    for(int v=1; v<=Ny; v++)
        if(!vst[v]&&g[u][v]&&dy[v]==dx[u]+1)
        {
            vst[v]=1;
            if(My[v]!=-1&&dy[v]==dis) continue;
            if(My[v]==-1||DFS(My[v]))
            {
                My[v]=u;
                Mx[u]=v;
                return 1;
            }
        }
    return 0;
}
int MaxMatch()
{
    int res=0;
    memset(Mx,-1,sizeof(Mx));
    memset(My,-1,sizeof(My));
    while(searchP())
    {
        memset(vst,0,sizeof(vst));
        for(int i=1; i<=Nx; i++)
            if(Mx[i]==-1&&DFS(i))  res++;
    }
    return res;
}

int main()
{
    int i,ans;
    int K,M,N;
    int u,v;
    while(~scanf("%d",&K))
    {
        if(K==0)break;
        scanf("%d%d",&M,&N);
        Nx=M;//匹配左边的顶点数
        Ny=N;//匹配右边的顶点数
        memset(g,0,sizeof(g));//二分图的邻接矩阵初始化
        for(i=0; i<K; ++i)
        {
            scanf("%d%d",&u,&v);
            g[u][v]=1;//顶点编号从1开始的
        }
        ans=MaxMatch();
        printf("%d
",ans);
    }
    return 0;
}
View Code

c5.Hopcroft-Karp算法(邻接表,用vector实现):

/*
//顶点编号从0开始的
二分图匹配(Hopcroft-Karp算法)
复杂度O(squt(n)*E)
邻接表存图,vector实现
vector先初始化,然后加入边
uN为左端的顶点数,使用前赋值(点编号0开始)
*/
#include<iostream>
#include<stdio.h>
#include<vector>
#include<queue>
#include<string.h>
using namespace std;

const int MAXN=512;
const int INF=0x3f3f3f3f;
vector<int>G[MAXN];
int uN;
int Mx[MAXN],My[MAXN];
int dx[MAXN],dy[MAXN];
int dis;
bool used[MAXN];
bool SearchP(){
    queue<int>Q;
    dis=INF;
    memset(dx,-1,sizeof(dx));
    memset(dy,-1,sizeof(dy));
    for(int i=0;i<uN;i++)
        if(Mx[i]==-1){
            Q.push(i);
            dx[i]=0;
        }
    while(!Q.empty()){
        int u=Q.front();
        Q.pop();
        if(dx[u]>dis)break;
        int sz=G[u].size();
        for(int i=0;i<sz;i++){
            int v=G[u][i];
            if(dy[v]==-1){
                dy[v]=dx[u]+1;
                if(My[v]==-1)dis=dy[v];
                else{
                    dx[My[v]]=dy[v]+1;
                    Q.push(My[v]);
                }
            }
        }
    }
    return dis!=INF;
}
bool DFS(int u){
    int sz=G[u].size();
    for(int i=0;i<sz;i++){
        int v=G[u][i];
        if(!used[v]&&dy[v]==dx[u]+1){
            used[v]=true;
            if(My[v]!=-1&&dy[v]==dis)continue;
            if(My[v]==-1||DFS(My[v])){
                My[v]=u;
                Mx[u]=v;
                return true;
            }
        }
    }
    return false;
}
int MaxMatch(){
    int res=0;
    memset(Mx,-1,sizeof(Mx));
    memset(My,-1,sizeof(My));
    while(SearchP()){
        memset(used,false,sizeof(used));
        for(int i=0;i<uN;i++)
            if(Mx[i]==-1&&DFS(i))
                res++;
    }
    return res;
}

int main(){
    int i,ans;
    int K,M,N;
    int u,v;
    while(~scanf("%d",&K)){
        if(K==0)break;
        scanf("%d%d",&M,&N);
        uN=M;//匹配左边的顶点数
        for(i=0;i<MAXN;++i)//二分图的邻接表初始化
            G[i].clear();
        for(i=0;i<K;++i){
            scanf("%d%d",&u,&v);
            G[--u].push_back(--v);//顶点编号从0开始的
        }
        ans=MaxMatch();
        printf("%d
",ans);
    }
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/gongpixin/p/5366827.html