二分图和匈牙利算法

一、二分图

1、思路

通过染色法判断二分图
二分法,就是可以把集合分为2部分,分别在每个集合内不存在边,有边只在两个集合之间。
一个图是二分图当且仅当图中不含有奇数环。
当染色过程中有矛盾时,就不是二分图。
用dfs或bfs遍历通过时染色


2、代码模板:


#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=2e5+10;

int n,m;
int h[N],e[N],ne[N],idx;
int color[N];//0代表还没染色,1代表白色,2代表黑色

void add(int a,int b)//添加一条从a到b的点,二分图只用加一个边就行,不用加两条边
{
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}

bool dfs(int u,int c)//将u染成c的颜色,并且往下继续染色,返回值为0代表有冲突,为1代表没有冲突。
{
    color[u]=c;
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(!color[j])//当前没有被染色
        {
            if(!dfs(j,3-c))//将下一个点染成相反的颜色,如果染色有冲突
                return false;
        }
        else if(color[j]==c)//如果邻点颜色和u相同,则说明出现奇环,返回false
            return false;
    }
    return true;//图中未发现奇环,是二分图
}

int main()
{
    memset(h,-1,sizeof h);
    scanf("%d %d",&n,&m);
    while(m--)
    {
        int a,b;
        scanf("%d %d",&a,&b);
        add(a,b);
        add(b,a);
    }
    bool flag=true;//1代表没有冲突,0代表有冲突
    for(int i=1;i<=n;i++)
    {
        if(!color[i])
        {
            if(!dfs(i,1))
            {
                flag=false;
                break;
            }
        }
    }
    if(flag) 
        puts("Yes");
    else 
        puts("No");
    return 0;
}


3、bfs版

代码思路
颜色 1 和 2 表示不同颜色, 0 表示未染***r /> 定义queue是存PII,表示 <点编号, 颜色>,
同理,遍历所有点, 将未染色的点都进行bfs
队列初始化将第i个点入队, 默认颜色可以是1或2
while (队列不空)
每次获取队头t, 并遍历队头t的所有邻边
若邻边的点未染色则染上与队头t相反的颜色,并添加到队列
若邻边的点已经染色且与队头t的颜色相同, 则返回false

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10;
typedef pair<int, int> PII;

int e[M], ne[M], h[N], idx;
int n, m;
int st[N];

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

bool bfs(int u){
    int hh = 0, tt = 0;
    PII q[N];
    q[0] = {u, 1};
    st[u] = 1;

    while(hh <= tt){
        auto t = q[hh ++];
        int ver = t.first, c = t.second;

        for (int i = h[ver]; i != -1; i = ne[i]){
            int j = e[i];

            if(!st[j])
            {
                st[j] = 3 - c;
                q[++ tt] = {j, 3 - c};
            }
            else if(st[j] == c) return false;
        }
    }

    return true;
}

int main(){
    scanf("%d%d", &n, &m);

    memset(h, -1, sizeof h);
    while(m --){
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b), add(b, a);
    }

    int flag = true;
    for(int i = 1; i <= n; i ++) {
        if (!st[i]){
            if(!bfs(i)){
                flag = false;
                break;
            }
        }
    }

    if (flag) puts("Yes");
    else puts("No");
    return 0;
}


二、匈牙利算法

1、思路

用来求2个集合之间连接的边的个数
每一个点只属于一个边
就算是无向边,存的边也只存左边的点指向右边的点
match存右边的点对应的左边的点
st防止重复搜点,res存匹配边的数量
遍历左集合的点,寻找匹配的边。



2、代码模板:

#include<iostream>
#include<cstring>

using namespace std;

const int N=1e5+10;

int n1,n2,m;//n1左集合,n2右集合
int h[N],e[N],ne[N],idx;
int match[N];//match存右边的点对应的左边的点
bool st[N];//st防止重复搜点,false是没有被遍历。true是被遍历过了
void add(int a,int b)
{
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
bool find(int x)//寻找第二个集合中与第一个集合x相匹配的下标
{
    for(int i=h[x];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(!st[j])//当j没有匹配时
        {
            st[j]=true;//标记一下j点已经被遍历
            if(match[j]==0||find(match[j]))//当j没有匹配的值时,或者j配对的点有下家时。
            {
                match[j]=x;
                return true;
            }
        }
    }
    return false;
}

int main()
{
    scanf("%d %d %d",&n1,&n2,&m);
    memset(h,-1,sizeof h);
    int res=0;
    while(m--)
    {
        int a,b;
        scanf("%d %d",&a,&b);
        add(a,b);
    }
    for(int i=1;i<=n1;i++)
    {
        memset(st,false,sizeof st);
        if(find(i))
            res++;
    }
    printf("%d",res);
    return 0;
}







































全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务