图论学习-二分图初步

二分图初步

一、二分图

1.1 二分图定义

图论中的一种特殊模型,设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且同一集合中不同的两点没有边相连。
在这里插入图片描述
上图即是一个明显的二分图。
一个基本性质:二分图不含奇数环,含奇数环的图一定不是二分图。

1.2 染色法判二分图

(1) 图中不存在奇数环<=>一个图是二分图(定义)
(2) 图中不存在奇数环<=>染色过程中不存在矛盾
code:

#include<bits/stdc++.h>
using namespace std;

const int N = 1e5+10,M = 2*N;
int h[N],e[M],ne[M],w[M],idx;
int color[N];
void add(int a,int b,int c)
{
    e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}
bool dfs(int u,int c)
{
    color[u] = c;
    for(int i = h[u];~i;i = ne[i])
    {
        int j = e[i];
        if(!color[j])
        {
            if(!dfs(j,3-c)) return false;
        }
        else
        {
            if(color[j]==c) return false;
        }
    }
    return true;
}
int main()
{
    memset(h,-1,sizeof h);
    int n,m;
    cin>>n>>m;
    for(int i = 1;i<=n;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    for(int i = 1;i<=n;i++)
    {
        if(!color[i]&&!dfs(i,1))
        {
            puts("No");
            return 0;
        }
    }
    puts("Yes");
    return 0;
}

例题:关押罪犯
二分+染色法

#include<bits/stdc++.h>
using namespace std;

const int N = 2e4+10,M = 2e5+10;
int h[N],e[M],ne[M],w[M],idx;
int n,m,mid;
int color[N];
void add(int a,int b,int c)
{
    e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}
bool dfs(int u,int c)
{
    color[u] = c;
    for(int i = h[u];i!=-1;i = ne[i])
    {
        if(w[i]<mid) continue;
        int j = e[i];
        if(!color[j])
        {
            if(!dfs(j,3-c)) return false;
        }
        else if(color[j]==c) return false;
    }
    return true;
}
bool check(int x)
{
    memset(color,0,sizeof color);
    for(int i = 1;i<=n;i++)
    {
        if(!color[i]&&!dfs(i,1))
        {
            return false;
        }
    }
    return true;
}
int main()
{
    memset(h, -1, sizeof h);
    cin>>n>>m;
    while (m -- ){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    int l = 0, r = 1e9;
    while(l < r){
        mid = l + r >> 1;

        if(check(mid)) r = mid;
        else l = mid + 1;
    }
    if(n <= 2) cout << 0; 
    else cout << l - 1;
    return 0;
}
/*
作者:cqust_qilin02811
链接:https://www.acwing.com/activity/content/code/content/2671431/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/

二、二分图匹配

2.0 定义

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

最大匹配:边数最多的一组匹配
匹配点:在匹配中的点
增广路径:从任意一个非匹配点走,通过非匹配边走到一个匹配点,然后通过匹配边走到匹配点…,最后一定通过一个非匹配边走到一个非匹配点,这样的一条路径就是增广路径。
重要结论:最大匹配等价于不存在增广路径

2.1 匈牙利算法

用处:可以求二分图的最大匹配数量
模板题
这里给出匈牙利算法的过程:

  1. 准备一个match数组,保存这个点对应的匹配点是谁。
  2. 图中所有点被划分为两部分,选择任意一部分作为出发点
  3. 遍历选择的那部分的所有点,看当前遍历点X所有邻边所能到达的点P,只要点P没有被访问过,就把它访问,然后给它标记匹配点:即match[P] = X;然后return true;如果访问过,就看当前match[P]所对应的那个点能不能找到新的点去匹配,如果可以,则match[P] = X;return true;

code:

#include<bits/stdc++.h>
using namespace std;

const int N = 510,M = 2e5+10;
int n1,n2,m;
int h[N],e[M],ne[M],idx;
int match[N];
bool st[N];
void add(int a,int b)
{
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
bool find(int u)
{
    for(int i = h[u];~i;i = ne[i])
    {
        int j = e[i];
        if(!st[j])
        {
            st[j] = true;
            if(!match[j]||find(match[j]))
            {
                match[j] = u;return true;
            }
        }
    }
    return false;
}
int main()
{
    memset(h, -1, sizeof h);
    cin>>n1>>n2>>m;
    while (m -- ){
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    int cnt = 0;
    for(int i = 1;i<=n1;i++)
    {
        memset(st, 0, sizeof st);
        if(find(i))
            cnt++;
    }
    cout<<cnt;
    return 0;
}

例题:棋盘覆盖
并不用建图,二维数组本身就是图,对于一半的点做最大匹配即可。
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;

const int N = 110;
bool g[N][N];
int n,m;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
typedef pair<int, int> PII;
#define x first
#define y second
PII match[N*N];
bool st[N*N];
bool find(int x,int y)
{
    for(int i = 0;i<4;i++)
    {
        int nx = dx[i]+x,ny = dy[i]+y;
        int id = (nx)*n+ny;
        if(nx<=0||ny<=0||nx>n||ny>n||st[id]||g[nx][ny]) continue;
        st[id] = true;
        PII t = match[id];
        if(t.x==-1||find(t.x,t.y))
        {
            match[id] = {x,y};
            return true;
        }
    }
    return false;
}
int main()
{
    memset(match,-1,sizeof match);
    cin>>n>>m;
    while (m -- ){
        int x,y;
        cin>>x>>y;
        g[x][y] = true;
    }
    int cnt = 0;
    for(int i = 1;i<=n;i++)
    {
        for(int j = 1;j<=n;j++)
        {
            if(g[i][j]) continue;
            memset(st, 0, sizeof st);
            if((i+j)%2==0&&find(i,j))
                cnt++;
        }
    }
    cout<<cnt;
    return 0;
}

三、最小覆盖问题

最大匹配数 = 最小点覆盖 = 总点数-最大独立集 = 总点数-最小路径覆盖

3.1 最小点覆盖

给定一个图(不一定是二分图),选出最少的点,使得所有的边能够被这些点访问到
最小点覆盖 = 最大匹配数(1)
例题:
机器任务
注意到让机器重启次数最小,实际上就是寻找最小点覆盖,也就是最大匹配数。
另外注意小细节,机器状态为0的时候不用重启,所以只需要建立状态大于等于1的图即可。

#include<bits/stdc++.h>
using namespace std;

const int N = 110,K = 1010,M = K*2;
int match[N];
int h[N],e[M],ne[M],idx;
int n,m,k;
bool st[N];
void add(int a,int b)
{
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
bool find(int u)
{
    for(int i = h[u];~i;i = ne[i])
    {
        int j = e[i];
        if(!st[j])
        {
            st[j] = true;
            if(!match[j]||find(match[j]))
            {
                match[j] = u;
                return true;
            }
        }
    }
    return false;
}
int main()
{
    while(cin>>n&&n)
    {  
        memset(h,-1,sizeof h);
        memset(match,0,sizeof match);
        cin>>m>>k;
        for(int i = 0;i<k;i++)
        {
            int id,a,b;
            cin>>id>>a>>b;
            if(!a||!b) continue;
            add(a,b);
        }
        int cnt = 0;
        for(int i = 1;i<=n;i++)
        {
            memset(st,0,sizeof st);
            if(find(i)) cnt++;
        }
        cout<<cnt<<endl;
    }
    return 0;
}

3.2 最大独立集与最大团

最大独立集定义:从一个图中选出最多的点,使得任意两个选出的点之间不存在边。
最大团定义:从一个图中选出最多的点,使得任意两个选出的点之间都存在边。
容易知道:最大团和最大独立集是互补关系。
原图的最大独立集就是补图的最大团。

在二分图中,求最大独立集:
等价于去掉最少的点使剩下的点都没有边。
等价于找最小点覆盖
等价于求最大匹配

总点数为n,最大匹配数量记为m
那么最大独立集 = n-m
例题:骑士放置
和棋盘覆盖那道题几乎一样。

code:

#include<bits/stdc++.h>
using namespace std;

const int N = 110;
typedef pair<int, int> PII;
int h[N],e[N*N],ne[N*N],idx,n,m,t;
bool st[N][N];
int g[N][N];
PII match[N][N];
int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};

#define x first
#define y second

void add(int a,int b)
{
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
bool find(int x,int y)
{
    for(int k = 0;k<8;k++)
    {
        int nx = x+dx[k],ny = y+dy[k];
        if(nx<=0||ny<=0||nx>n||ny>m||g[nx][ny]) continue;        
        if(st[nx][ny]) continue;
        st[nx][ny] = true;
        PII &t = match[nx][ny];
        if(t.x==0||find(t.x,t.y))
        {
            match[nx][ny] = {x,y};
            return true;
        }
    }
    return false;
}
int main()
{
    memset(g,0,sizeof g);
    cin>>n>>m>>t;
    for(int i = 0;i<t;i++)
    {
        int x,y;
        cin>>x>>y;
        g[x][y] = 1;
    }
    int cnt = 0;
    for(int i = 1;i<=n;i++)
    {
        for(int j = 1;j<=m;j++)
        {
            if(g[i][j]||(i+j)%2==1) continue;            
            memset(st, 0, sizeof st);
            if(find(i,j)) cnt++;
        }
    }
    cout<<n*m-cnt-t;
    return 0;
}

3.3 最小路径点覆盖与最小路径重复点覆盖

最小路径点覆盖:
简称为最小路径覆盖。
针对有向无环图(DAG),最少用多少条互不相交的路径,可以将所有点覆盖。
互不相交:(点不重复,边也不重复)

技巧:拆点建图,把一个点分成两个点,分别为出点和入点
在这里插入图片描述
容易发现,拆点过后,得到的图一定是一个二分图


最小路径重复点覆盖:
即用多少条路径可以把图的所有点覆盖(不需要互不相交)

  1. 求传递闭包G’
  2. 在G’上求最小路径覆盖

求原图中互不相交路径数等价于求最大匹配。
详细证明见:大佬链接

参考资料

acwing算法基础课、提高课
大佬博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值