二分图

二分图

染色法(判断是不是二分图)

时间复杂度: O ( n + m ) O(n+m) O(n+m)

什么是二分图

可以把所有的点分成两边,使得所有的边都是在集合之间的(集合内部没有边)

二分图的性质

一个图是二分图,当且仅当图中不含奇数环,等价于用染色法染色不存在矛盾(奇数环:环当中边的数量是奇数)

image-20210108112312938

必要性证明: 给定一个奇数环,可以推出一个点既在左边,又在右边

充分性证明:从前往后遍历所有点,和黑色相邻的点是白色和白色相邻的点是黑色—由于图中不含奇数环,所以染色过程中一定没有矛盾(反证法:假设在染色过程中出现了矛盾,

染色法

  • f o r   i : 1 → n for\ i:1\to n for i:1n
    • i f   v if\ v if v 未染色
      • d f s ( i ) dfs(i) dfs(i)

Code

860. 染色法判定二分图

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

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

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7;
const int N = 1e6 + 7, M = N * 2;

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

void add(int a, int b) {
    e[idx] = b, 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]) {
        int j = e[i];
        if (!color[j]) {
            if (!dfs(j, 3 - c)) return false; //如果当前是1,变为2,如果当前是2 变为1
        } else if (color[j] == c) return false;
    }

    return true;
}


int main() {
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof(h));
    for (int i = 1; i <= m; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        add(u, v), add(v, u);
    }

    int flag = true; //标记是否出现了矛盾
    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;
}

匈牙利算法 (求二分图最大匹配)

最大匹配:边数最多的一组匹配,等价于不存在增广路径

匹配点:在匹配当中的点

非匹配点:不在匹配当中的点

增广路径:从一个非匹配点走,沿着非匹配边,走到一个匹配点,再沿着匹配边走,再沿着非匹配边走…(开始的左边是非匹配点,右边是非匹配点)

image-20210310160006941

时间复杂度: O ( n m ) O(nm) O(nm),实际远小于 O ( n m ) O(nm) O(nm)

在一个比较快的时间内求出左边和右边匹配成功的最大数量(没有两条边是共用了一个点的)

基本思路

  1. 对左半边的点进行匹配,如果左半边点有链接右半边点的路径,把他们相连
  2. 如果一个点的出边已经被其他点相连,询问这个已经被连的点,看看它连它的点是不是可以连其他点。尝试所有可能之后,都不能联通,才放弃。

Code

861. 二分图的最大匹配

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

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

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

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

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

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 1e6 + 7, M = N * 2;

int n1, n2, m;
int h[N], e[M], ne[M], idx;
int match[N];
bool st[510];

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

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 main() {
    cin >> n1 >> n2 >> m;
    memset(h, -1, sizeof(h));

    int u, v;
    for (int i = 1; i <= m; i++) {
        cin >> u >> v;
        add(u, v);
    }

    int ans = 0;
    for (int i = 1; i <= n1; i++) {
        memset(st, false, sizeof(st));
        if (find(i)) ans++;
    }

    cout << ans;
    return 0;
}

习题

372. 棋盘覆盖

覆盖问题

结论

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

最小点覆盖

定义:给定一个图,从中选出最少的点,使得每条边的两个端点中至少有一个是被选出来的

image-20210310171207807

二分图中,最小点覆盖等于最大匹配数。

证明

  • 最小点覆盖 ≥ \ge 最大匹配数

在二分图的一组匹配中,匹配边是没有公共点的,如果最大匹配数为 m m m ,需要覆盖所有边,最小的覆盖点一定要大于 m m m

  • 最小点覆盖 = = = 最大匹配数 的等号可以成立

使用构造方法

最大独立集

最大独立集:从一个图当中选出最多的点,使得选出的点之间是没有边的(但是可以和独立集之外的点连边),点数最多的集合是最大独立集

最大团:从一个图中选出最多的点,使得任意两点之间都有边

补图:把之前有连边的点删掉边,把没有连边的点连上边,这样就求得了一个图的补图

注意:设原图为 G G G G G G 的补图为 G ′ G' G ,原图的最大独立集就是补图的最大团。

在二分图中,如果要求最大独立集,等价于选出最多的点,使得选出的点之间是没有边的,等价于去掉最少的点,破坏所有边,等价于找到最少的点,覆盖所有的边(最小点覆盖),等价于找最大匹配

设去掉 m m m 个点,总共有 n n n 个点,最大独立集就是 n − m n-m nm

注意:在普通图中,找最大独立集是一个 N P − h a r d NP-hard NPhard 问题,没有多项式时间的解

最小路径点覆盖(最小路径重复点覆盖)

也被称为最小路径覆盖,针对一个有向无环图 ( D A G DAG DAG),希望用最少的互不相交(点不重复)的路径将所有点覆盖住

结论:最小路径点覆盖 = 总点数 - 最大匹配数

  • 建图

求最小路径覆盖用到拆点,把一个点分成两个点,分别表示出点和入点,那么从点 i → j i\to j ij 的一条边就用,从左边的出点 i i i 连到右边的入点 j ’ j’ j 表示,
于是得到的图是一个二分图,因为所有的边都是在左部和右部之间的,内部没有点

  • 转化

此时将原图中的每一条路径转化到新图中,因为原图中的路径互不相交,所以每一个点最多只有一个出度和入度

这就意味着,在新图中,左部每一个点最多只会向右部连一条边,右部的点最多只会有一条边连入,每个点最多只会属于一条边。

  1. 原图中的一条路径 ⇔ \Leftrightarrow 新图中的一组匹配(新图中每一个点最多只会属于一条边)
  2. 原图中每一条路径的终点(没有出边) ⇔ \Leftrightarrow 新图左部的非匹配点
  • 推导

求原图中互不相交路径数 ⇔ \Leftrightarrow 求路径终点数最少 ⇔ \Leftrightarrow 求左部非匹配点最少 ⇔ \Leftrightarrow 求最大匹配

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KwVZXplF-1615859122578)(https://i.loli.net/2021/03/15/RDa5yYXtUqxV6mn.png)]

最小路径重复点覆盖

针对一个有向无环图 ( D A G DAG DAG),希望用最少的互不相交(点不重复)的路径将所有点覆盖住。即,在最小路径覆盖问题的基础上,去掉互不相交。

  1. 设原图为 G G G 求传递闭包 G ′ G' G ,在 G ′ G' G 上求最小路径覆盖
  2. 原图的最小路径重复点覆盖就等于新图的最小路径覆盖

可以用二分图但是效果不好

最优匹配 (KM)建议最小费用流

多重匹配 (每个点可以匹配多个点)建议最大流

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值