ACwing算法备战蓝桥杯——Day20——二分图

定义:

二分图中的结点只有两种属性,两个相邻两个结点只能具有不同的属性;

可以抽象成将两种属性的点划分成两个集合,同一集合的点之间没有边;

查看一个图是否为二分图:

染色法

思路:

对于一个图的结点,枚举每个结点,如果遇到一个未染色的结点,就进行dfs(或者bfs也行),遍历当前结点的连通块,途中进行染色(结点的颜色就两种,代表两种属性)。

dfs返回一个布尔值,如果为假,就说明当前连通块中有奇数环,就是相邻两个结点的属性相同,无法构成二分图。

模板:

int n;      // n表示点数
int h[N], e[M], ne[M], idx;     // 邻接表存储图
int color[N];       // 表示每个点的颜色,-1表示未染色,0表示白色,1表示黑色

// 参数:u表示当前节点,c表示当前点的颜色
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] == -1)
        {
            if (!dfs(j, !c)) return false;
        }
        else if (color[j] == c) return false;
    }

    return true;
}

bool check()
{
    memset(color, -1, sizeof color);
    bool flag = true;
    for (int i = 1; i <= n; i ++ )
        if (color[i] == -1)
            if (!dfs(i, 0))
            {
                flag = false;
                break;
            }
    return flag;
}

模板题:

给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环。

请你判断这个图是否是二分图。

输入格式
第一行包含两个整数 n 和 m。

接下来 m 行,每行包含两个整数 u 和 v,表示点 u 和点 v 之间存在一条边。

输出格式
如果给定图是二分图,则输出 Yes,否则输出 No。

数据范围
1≤n,m≤105
输入样例:
4 4
1 3
1 4
2 3
2 4
输出样例:
Yes

代码:
#include 
#include 
using namespace std;
const int N=1e5+10,M=2*N;

int n,m;

int h[N],ne[M],e[M],idex;
int color[N];

void add(int a,int b){
    ne[idex]=h[a],e[idex]=b,h[a]=idex++;
}

bool dfs(int loc,int c){//c取1或2
    
    color[loc]=c;
    
    for(int i=h[loc];i!=-1;i=ne[i]){
        int j=e[i];
        
        if(!color[j]){
            
            if(!dfs(j,3-c)) return false;
        }
        
        if(color[j]==c) return false;
        
    }
    
    return true;
}



int main(){
    
    scanf("%d%d",&n,&m);
    
    memset(h,-1,sizeof h);
    
    for(int i=0;i

二分图的最大匹配数量(匈牙利算法):

解释:

二分图中的两个集合间的连线的最大数量,连线的要求是两个集合间的点的连线是一一对应的,不存在一对多或者多对一的情况;

思路:

主要内容

参数

返回值/储存值

作用

函数

find()

第一个集合内的点

布尔值,是否能找到匹配

寻找第一个集合的点是否能获得匹配

全局数组

match[]

第二个集合内的点

第一个集合内的点

储存与第二个集合内的点匹配的第一个集合内的点

局部数组

st[]

第二个集合内的点

布尔值,是否询问过

标记第二个集合内的点是否被询问过,在调整分配的时候防止重复询问第二个集合内的某一个点

  • 枚举第一个集合的点,建立一个find()函数判断当前这个点能否匹配到另一集合内的点,返回一个布尔值。

  • 现在假设有第一个集合里的点i,第二个集合里的点 j,j是i的一个匹配;

  • 在find()函数中:

  • 如果j为被匹配,就用match[]数组储存点i的标识信息,并返回true;

  • 反之,如果遇到匹配过的,先将当前st[j]标为true;

  • 再将当前这个match[]里已有的第一个集合的点进行再匹配(递归),让它去找另一个能匹配的点,将当前这个第二个集合里的点让出给i。

  • 如果再匹配找不到,就返回false;

易错点

st数组每次枚举都要初始化,容易忘记,要记牢!!!

模板:

int n1, n2;     // n1表示第一个集合中的点数,n2表示第二个集合中的点数
int h[N], e[M], ne[M], idx;     // 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边
int match[N];       // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个
bool st[N];     // 表示第二个集合中的每个点是否已经被遍历过

bool find(int x)
{
    for (int i = h[x]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j])
        {
            st[j] = true;
            if (match[j] == 0 || find(match[j]))
            {
                match[j] = x;
                return true;
            }
        }
    }

    return false;
}

// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点
int res = 0;
for (int i = 1; i <= n1; i ++ )
{
    memset(st, false, sizeof st);
    if (find(i)) res ++ ;
}

模板题:

给定一个二分图,其中左半部包含 n1 个点(编号 1∼n1),右半部包含 n2 个点(编号 1∼n2),二分图共包含 m 条边。

数据保证任意一条边的两个端点都不可能在同一部分中。

请你求出二分图的最大匹配数。

二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。

二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。

输入格式
第一行包含三个整数 n1、 n2 和 m。

接下来 m 行,每行包含两个整数 u 和 v,表示左半部点集中的点 u 和右半部点集中的点 v 之间存在一条边。

输出格式
输出一个整数,表示二分图的最大匹配数。

数据范围
1≤n1,n2≤500,
1≤u≤n1,
1≤v≤n2,
1≤m≤105
输入样例:
2 2 4
1 1
1 2
2 1
2 2
输出样例:
2

代码:
#include 
#include 
using namespace std;

const int N=510;

int n1,n2,m;
bool g[N][N];
bool st[N];
int match[N];

bool find(int x){
        
    for(int i=1;i<=n2;i++){
        //如果有边
        if(g[x][i]){
            //如果未询问过
            if(!st[i]){
                
                st[i]=true;
                
                if(match[i]==0||find(match[i])){
                    
                    match[i]=x;
                    
                    return true;
                    
                }                
            }
        }
    }
    return false;
}


int main(){
    
    cin>>n1>>n2>>m;
    
    for(int i=0;i

例题:

完美牛棚:

农夫约翰上周刚刚建好了新的牛棚,并引进了最新的挤奶技术。

不幸的是,由于工程问题,牛棚中的每个单间都不太一样。

第一周,约翰将奶牛们随机分配在了各个单间中。

但是很快他就发现,每头奶牛都只愿意在一部分自己喜欢的单间中产奶。

在过去的一周中,农夫约翰一直在收集有关哪些奶牛愿意在哪些单间产奶的数据。

一个单间只能分配给一头奶牛,当然,一头奶牛也可能只愿意在一个单间中产奶。

给定奶牛的住宿喜好,请你计算,通过合理分配奶牛的住所,最多能够让多少奶牛可以住在自己愿意产奶的单间中。

输入格式
第一行包含两个整数 N 和 M,分别表示奶牛的数量以及单间的数量。

接下来 N 行,每行记录一个奶牛愿意产奶的单间信息。首先包含一个整数 Si,表示这头奶牛愿意在 Si 个单间中产奶。接下来包含 Si 个不同的整数,表示这些单间的编号。

所有单间的编号为 1∼M。

输出格式
输出一个整数,表示可以被分配在自己愿意产奶的单间中的奶牛的最大数量。

数据范围
0≤N,M≤200
0≤Si≤M
输入样例:
5 5
2 2 5
3 2 3 4
2 1 5
3 1 2 5
1 2
输出样例:
4

代码:
//题意保证了是二分图,牛是一个集合,牛棚是另一个集合
//匈牙利算法
#include 
#include 

using namespace std;

const int N=210,M=N*N;

int n,m;
int e[M],ne[M],h[N],idex;
int match[M];//存储的是第i个牛棚住的哪头牛
bool st[M];

void add(int a,int b){
    ne[idex]=h[a],e[idex]=b,h[a]=idex++;
}

bool find(int x){//牛x能否住进偏好的牛棚
    
    for(int i=h[x];i!=-1;i=ne[i]){//遍历牛x的偏好牛棚
    
        int j=e[i];
        
        if(!st[j]){
            
            st[j]=true;//不能一直找同一个牛棚
                
            if(match[j]==0||find(match[j])){//如果当前牛棚还没住牛z或者当前牛棚的牛能找到另一个牛棚
            
                match[j]=x;
                
                return  true;
            }
        
        }

    }
    return false;
}

int main(){
    
    scanf("%d%d",&n,&m);
    
    memset(h,-1,sizeof h);
    
    for(int i=1;i<=n;i++){
        
        int k;
        
        scanf("%d",&k);
        
        for(int j=0;j

你可能感兴趣的:(算法学习笔记,算法,蓝桥杯,c++)