【蓝桥杯】二分图

一.定义

二分图又称作偶图。就是顶点集 V 可分割为两个互不相交的子集,且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻。

当图中的顶点分为两个集合,使得第一个集合中的所有顶点都与第二个集合中的所有顶点相连时,此时是一特殊的二分图,称为完全二分图。

二.染色法判断二分图

        通过二分图的定义我们可以知道,同一个点集中任意两个点之间没有边,也就是说一条边的两个顶点一定不在同一个集合。

        然后我们可以根据这个性质给每个点设置一个标记,如果一个点标记为1,那么将它的直接后继结点标记为2,如果一个点被标记两次不同的值,那么说明图中存在奇数边的环,该图不是二分图。 

        更形象的来说,用染色的方式给每个点依次进行染色,如果一个点被染上两种颜色,则该图不是二分图。

 DFS

//给定一个无向图,判断是否是二分图
#include <iostream>
#include <cstring>
using namespace std;

const long long N=1e3+10;

int edge[N][N]={0};//邻接矩阵存储
int color[N]={0};//1表示红色 -1表示黑色 0表示未染色
int n,m;//n个顶点m条边

bool dfs(int v,int c){
    color[v]=c;//对v点进行染色
    for(int i=1;i<=n;i++){//遍历与v点相连的点
        if(edge[v][i]==1){
            if(color[i]==c){//i点颜色与v相同,不是二分图
                return false;
            }
            else if(color[i]==0){//i点没有染色
                dfs(i, -c);//i点染相反的颜色
                return false;
            }
            else{
                continue;
            }
        }
    }
    return true;
}
int main(int argc, const char * argv[]) {
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n>>m;
    int u,v;
    for(int i=0;i<m;i++){
        cin>>u>>v;
        edge[u][v]=1;
        edge[v][u]=1;
    }
    cout<<dfs(1,1);
    return 0;
}

三.匈牙利算法 

匈牙利算法:

图论中寻找最大匹配的算法(暂不考虑加权的最大匹配),主要用于解决一些与二分图匹配有关的问题。

1.匹配:

1)匹配是边的集合

2)在该集合中,任意两条边不能有共同的顶点

2.最大匹配:

选择这样的边数最大的子集称为图的最大匹配问题。最大匹配的边数称为最大匹配。

3.交替路:

从未匹配点出发,依次经过未匹配的边和已匹配的边,即为交替路。

4.最小覆盖:

二分图的最小覆盖分为最小顶点覆盖最小路径覆盖:

1)最小顶点覆盖是指,最少的顶点数使得二分图G中的每条边都至少与其中一个点相关联,二分图的最小顶点覆盖数=二分图的最大匹配数;

2)最小路径覆盖也称为最小边覆盖,是指用尽量少的不相交简单路径覆盖二分图中的所有顶点。二分图的最小路径覆盖数=|V|-二分图的最大匹配数;

5.增广路:

如果交替路经过除出发点外的另一个未匹配点,则这条交替路称为增广路。

由增广路的定义推出下面三个结论(设P为一条增广路):
1) P的路径长度一定为奇数,第一条边和最后一条边都是未匹配的边。

2) 对增广路径编号,所有奇数的边都不在M中,偶数边在M中。

3) P经过取反操作可以得到一个更大的匹配图,比原来匹配多一个(取反操作即,未匹配的边变成匹配的边,匹配的边变成未匹配的边)

4) 当且仅当不存在关于图M的增广路径,则图M为最大匹配。所以匈牙利算法的思路就是:不停找增广路,并增加匹配的个数。

//寻找二分图的最大匹配
#include <iostream>
#include <cstring>

using namespace std;

const long long N=1e3+10;

int n,m;//n,m分别表示左侧、右侧集合的元素数量
int edge[N][N];//邻接矩阵存储图
int p[N];//记录右侧元素所对应的左侧元素
bool vis[N];//记录右侧元素是否已经被访问过

bool match(int i){
    for(int j=1;j<=m;++j){
        if(edge[i][j]&&!vis[j]){//有边且未访问过
            vis[j]=true;    //设置为访问过
            if(p[j]==0||match(p[j])){//暂时无匹配,或者原来匹配的左侧元素可以找到新的匹配
                p[j]=i;
                return true;
            }
        }
    }
    return false;
}

int Hungarian(){
    int cnt=0;
    for(int i=1;i<=n;i++){
        memset(vis,false,sizeof(vis));//重置vis数组
        if(match(i)){
            cnt++;
        }
    }
    return cnt;
}
//寻找二分图的最大匹配
#include <iostream>
#include <cstring>

using namespace std;

const long long N=1e3+10;

typedef struct graph{
    int m,n;//图左侧有m个顶点,右侧有n个顶点
    int edge[N][N];//邻接矩阵
    bool on_path[N];//该点是否在增广路径上
    int path[N];//记录路径
    int max_match;
}GRAPH_MATCH;

void output(GRAPH_MATCH *match){
    for(int i=0;i<match->n;i++){
        cout<<match->path[i]<<"---"<<i<<'\n';
    }
}

bool find_aug_path(GRAPH_MATCH *match,int i){
    for(int j=0;j<match->n;j++){
        if(match->edge[i][j]&&match->on_path[j]==false){//如果i与j之间存在路径,并且j没有在已经存在的增广路径上
            match->on_path[j]=true;
            if(match->path[j]==-1||find_aug_path(match, match->path[j])){
                //如果j是未覆盖的点或者说和j相连的点能够找到新的点与之相连
                match->path[j]=i;
                return true;
            }
        }
    }
    return false;
}

void Hungary_match(GRAPH_MATCH *match){
    for(int i=0;i<match->m;i++){
        find_aug_path(match, i);
        memset(match->on_path,false,sizeof(match->on_path));
    }
}

int main(int argc, const char * argv[]) {
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    GRAPH_MATCH *match=new GRAPH_MATCH;
    cin>>match->m>>match->n;
    memset(match->on_path,false,sizeof(match->on_path));
    memset(match->path,-1,sizeof(match->path));
    int edge,u,v;
    cin>>edge;
    for(int i=0;i<edge;i++){
        cin>>u>>v;
        match->edge[u][v]=1;
    }
    return 0;
}

四.实战演练

谈恋爱

1.题目描述:

有a个男生,编号1,...,a,和b个女生,编号1,...,b。

u_i个男生想和第v_i个女生谈恋爱。

在女生都愿意的情况下,最多出现几对情侣。

2.输入描述:

第一行为a,b,m。

接下来的m行每行包含两个正整数u,v,保证1\leq u\leq a,1\leq v \leq b

1 \leq a,b \leq 500, 1\leq m \leq 250000

3.输出描述:

一个整数,表示最多产生多少对情侣。

4.代码实现:

//谈恋爱
#include <iostream>
#include <cstring>

using namespace std;

const long long N=5e2+10;
int a,b,m;

int edge[N][N]={0};
int p[N]={0};//女生所对的男生
bool vis[N]={false};//女生是否被访问过

bool match(int i){
    for(int j=1;j<=b;j++){
        if(edge[i][j]&&!vis[j]){//有边并且没有被访问过
            vis[j]=true;
            if(p[j]==0||match(p[j])){//没有匹配或者说原来匹配的左侧元素可以找到新的元素进行匹配
                p[j]=i;
                return true;
            }
        }
    }
    return false;
}

int Hungarin(){
    int cnt=0;
    for(int i=1;i<=a;i++){
        memset(vis,false,sizeof(vis));
        if(match(i)){
            cnt++;
        }
    }
    return cnt;
}

int main(int argc, const char * argv[]) {
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>a>>b>>m;
    int u,v;
    for(int i=0;i<m;i++){
        cin>>u>>v;
        edge[u][v]=1;
    }
    cout<<Hungarin()<<'\n';
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值