【学习笔记】二分图

感觉需要恶补一下二分图。

二分图的概念

对于一个无向图,如果其点集能被分为两部分,使得同一部内没有连边,那么这个图就是一个二分图

二分图有几个基本的性质:

  • 一个二分图可以被黑白染色,并且对其进行黑白染色后,同一部的颜色相同、
  • 从二分图的某一个点开始 BFS,那么所在的奇数层和偶数层分别为左部和右部的点。
  • 一个二分图的每一个环的长度都是偶数。

二分图的匹配

对于一个二分图,若它的边集的一个子集满足每两条边没有公共端点,那么称这个边集为一个匹配。

很多问题都可以转化为二分图的匹配问题,比如变量与值的匹配。

匈牙利算法

匈牙利算法的核心思想就是每一次从左部的一个点开始,寻找一个增广路,满足这条路径上匹配的边和未匹配的边交替出现,最后以未匹配的边结束。这条路的长度为奇数,然后将这条增广路上的边全部翻转,这样每一次就会使得匹配的边集扩大 1,若无增广路则得到了最大匹配。

bool match(int i) {
    if (vis[i]) return false;
    vis[i] = 1;
    for (int j : e[i]) if (!p[j] || match(p[j])) {
        p[j] = i;
        return true;
    }
    return false;
}
int hungarian() {
    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        memset(vis, 0, sizeof vis);
        if (match(i)) cnt++;
    }
    return cnt;
}

一共 DFS n 遍,于是复杂度为 O(nm)

网络流做法

考虑直接建网络流,源点连左部,右部连汇点,直接跑就行。

考虑每次 BFS+DFS 是 O(m) 的,而对于前 m 轮增广复杂度为 O(mm),而后 m 次增广,增广路径长度大于 m,这样的路径不超过 m 条,于是最多只会再进行 m 轮增广,于是复杂度为 O(mm)

最大权匹配

直接跑费用流吧

upd. 我错了,别跑费用流吧

KM 算法:求最大权完美匹配。如果两边点数不一样就补一下让两边一样。

考虑给每个点赋一个权值 li,要求满足 w(u,v)lu+lv,称为可行顶标。我们记左部点的可行顶标为 lxi,右部点的可行顶标为 lyi

我们定义相等子图为,将所有满足 w(u,v)=lxu+lyv 的边保留下来得到的图。有定理:若存在某组可行顶标满足相等子图存在完美匹配,那么这组匹配就是最大全完美匹配。证明就放缩一下,可以得到匹配的权值一定小于等于 lxi+lyi,若存在完美匹配那么说明这组匹配一定取到了上界。

那么考虑类似于匈牙利的做法,每次考虑找到一个点在相等子图上的匹配,如果能增广成功则结束,否则考虑调整当前的可行顶标使得更多的边加入相等子图,进而存在增广路让匹配增加。考虑交错树(即匈牙利遍历到的所有点形成的树)上的点,记树上的左部点集合为 S,右部点集合为 T,不存在于交错树的集合为 ST。容易得到,不存在 ST 的边且不存在 ST 的匹配边(否则交错树可以扩大)。

考虑将 S 中的所有点减 dT 中的所有点加 d,那么 ST 的边仍然在相等子图内,ST 内的边不会变,ST 中的 lxu+lyv 减少,可能加入相等子图,ST 中的 lxu+lyv 增加,使得全部从相等子图内移除,由于其本来就不存在匹配边所以不会影响最大匹配。那么我们令 dST 的边中 lxu+lyvw(u,v) 的最小值,这样会使得恰好这一条边加入相等子图。于是我们对 T 中的点维护 slack(v)=min{lxu+lyvw(u,v)|uS},那么我们就可以 O(n) 计算出 d=min{slack(v)|vT}

每个点可能会进行 O(n) 次调整,每次调整后重新匹配是 O(n2) 的,于是朴素实现是 O(n4) 的。

const long long INF = 0x3f3f3f3f3f3f3f3f;
long long c[MAXN][MAXN], lx[MAXN], ly[MAXN], slk[MAXN];
int match[MAXN];
bool visx[MAXN], visy[MAXN];
bool dfs(int u) {
    visx[u] = 1;
    for (int v = 1; v <= n; v++) if (!visy[v]) {
        long long t = lx[u] + ly[v] - c[u][v];
        if (t) {
            slk[v] = min(slk[v], t);
        } else {
            visy[v] = 1;
            if (!match[v] || dfs(match[v])) { match[v] = u; return true; }
        }
    }
    return false;
}
long long km() {
    for (int i = 1; i <= n; i++) lx[i] = ly[i] = match[i] = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) lx[i] = max(lx[i], c[i][j]);
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) slk[j] = INF;
        while (1) {
            for (int j = 1; j <= n; j++) visx[j] = visy[j] = 0;
            if (dfs(i)) break;
            long long d = INF;
            for (int j = 1; j <= n; j++) if (!visy[j]) d = min(d, slk[j]);
            for (int j = 1; j <= n; j++) if (visx[j]) lx[j] -= d;
            for (int j = 1; j <= n; j++) if (visy[j]) ly[j] += d; else slk[j] -= d;
        }
    }
    long long ans = 0;
    for (int i = 1; i <= n; i++) ans += c[match[i]][i];
    return ans;
}

注意到问题在于每次重新匹配太浪费时间,所以可以复用上一次的匹配结果,从加入的边开始进行增广。额我真看不下去了先咕了,如果真碰到题卡我这个了再回来学吧。

最大匹配相关的转化

最小点覆盖 = 最大匹配

最大独立集 = n - 最小点覆盖

完美匹配

完美匹配即匹配的数量等于左部点的数量的匹配。

Hall 定理

一个二分图存在完美匹配的充要条件为:对于左部点的任意一个子集,其相连的右部的点集大小都大于等于这个子集的大小。

例子:

ai[li,ri],并且 aiS,求是否存在一组 ai 的解,使得 ai 互不相同。

将左侧 i 向右侧 [li,ri] 连边,就变成了是否存在完美匹配的问题。

考虑右部的点的集合,如果为几段不连续的区间,那么我们可以将它划分为几个不相交的子问题。

所以我们只考虑连续的区间,那么只需要满足左侧点的数量小于等于右侧点的数量,于是可以推出 LR,[LliriR]RL+1i=LR[iS]

证明:如果 [L,R] 是原来的一个区间,那么这个式子肯定没问题,如果不是原来的一个区间,那么必定会存在一个 [L,R][L,R][LliriR]=[LliriR],这时候已知 [L,R] 对于上式是成立的,那么 [L,R] 也一定成立。

二分图匹配的可行边和必须边

必须边的定义:二分图的任意一个最大匹配都包含的边。

可行边的定义:至少属于一个二分图的最大匹配的边。

首先对二分图跑一遍网络流。

必须边的判定条件:边的流量为 1,并且两个端点在残量网络中不属于同一个 SCC。

可行边的判定条件:边的流量为 1,或两个端点在残量网络中属于同一个 SCC。

这个东西与网络流中最小割的可行边和必须边是等价的。P4126

网格图模型

对网格图进行黑白染色,则图上相邻的两个点必定为一个黑点一个白点。P5038

P5030:这题比较有意思,可以发现能互相攻击到的点必定在同一种颜色的格子上,且横坐标的奇偶性一定不同,于是可以根据奇偶性来划分左部和右部,然后就是二分图最大匹配问题了。

posted @   APJifengc  阅读(135)  评论(4编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示