• 二分图


    二分图的相关概念

    二分图:二分图又称二部图,是图论中的一种特殊模型。设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
  • 相关阅读:
    二叉树
    bfs
    E-Gold Coins
    D-We Love MOE Girls
    A
    哈希--查找出现过的数字
    二分查找
    KMP简单应用
    KMP算法(2)
    [JSOI2008]最大数
  • 原文地址:https://www.cnblogs.com/gongpixin/p/5366827.html
Copyright © 2020-2023  润新知