← 返回 Blog
GRAPH / LAB返回座位题

BIPARTITE MATCHING · FROM ONE EXAMPLE

二分图匹配

三位候选人竞争三个岗位。最基本的枚举能够得到答案,但方案数量增长过快。 问题的特殊结构将搜索缩减为一次次增广。

枚举方案关系表二分结构匹配增广路最大匹配

01 · 基本方案

枚举所有录取方案

本章作用先建立一个一定正确的解法,用实际执行确认题目究竟要求选择什么。
主线知识

候选人逐个选择岗位或不录取;岗位不能重复使用;记录最多录取人数。

扩展知识

暂不引入图、匹配或增广路。

招聘组有三位候选人和三个岗位。一次录取必须满足技能要求,每个人和每个岗位最多使用一次。

阿青前端后端
小北前端
晨晨后端测试
分支 A
  1. 阿青 → 前端
  2. 小北 → 无可用岗位
  3. 晨晨 → 后端
录取 2 人
分支 B
  1. 阿青 → 后端
  2. 小北 → 前端
  3. 晨晨 → 测试
录取 3 人

递归枚举每位候选人的所有选择,遇到岗位重复或技能不符就丢弃该分支,最终取最大值。 这个方法能够正确得到 3,因此小数据下问题已经解决。

执行规模(m + 1)n

n 位候选人各自可能选择 m 个岗位或“不录取”,粗略上界达到 (m+1)n。 当 n=m=20 时,需要面对 2120 个原始分支。困难不在检查某一个方案,而在方案数量随人数指数增长。

剩余问题完整枚举正确,但不能承受更大的输入规模。

02 · 关系表达

关系表与图模型

本章作用分析枚举中的重复工作,把技能判断从搜索过程里分离出来。
主线知识

用二维表预存谁能胜任谁,再把对象和两两关系表示成点与边。

扩展知识

图在此只负责表达关系,还没有降低方案数量。

枚举的每个分支都会重新询问“候选人能否胜任岗位”。技能关系并不会随分支改变, 因此可以先保存为二维表。重复检查被消除,但录取组合仍需继续枚举。

候选人前端后端测试
阿青
小北
晨晨

二维表与下面的图表达同一份数据:对象变成点,表中的一个 ✓ 变成连接两个对象的边。 图模型解决的是关系表达,并不会自动把指数搜索变快。

全部合法关系5 条线都只表示“能胜任”

此时还没有决定录取谁。

合法但未选择
后文固定使用这一张关系图。

六个点和五条合法边不再改变;后续只讨论从这些边中怎样选择。

剩余问题关系已经清晰,但从合法边中寻找最优组合仍然困难。

03 · 特殊结构

二分图

本章作用一般的关系图并未降低求解难度,因此检查当前图是否具有可利用的特殊结构。
主线知识

两组顶点、组内无边、二染色判定、奇环反例,以及结构对后续搜索的作用。

扩展知识

二分图上的着色、覆盖、独立集、加权匹配与博弈。

左边所有点都是候选人,右边所有点都是岗位。边只描述“候选人能否胜任岗位”, 所以候选人之间没有边,岗位之间也没有边;每一条线都有明确的左端点和右端点。

这不是为了套算法而强行分组,而是关系本身就有两种角色。把两组记为 LR,边集记为 E,便得到G = (L ∪ R, E),并且 E ⊆ L × R

L候选人

阿青、小北、晨晨

每条边只跨组连接
R岗位

前端、后端、测试

偶环能够使用两种颜色染色
二染色:沿每条边切换颜色。图能够完成二染色,当且仅当它是二分图。
三角形二染色失败
奇环反例:三角形中最后一条边的两个端点被迫同色,因此无法分成两个组内无边的集合。
结构带来的约束每次沿边移动都会从 L 到 R,或从 R 回到 L。

组内无边使搜索方向稳定交替。后续“尝试新关系—找到旧搭档”的路径因此可以按两种边交替组织。

扩展:二分结构还使哪些问题变得更容易?
极大团

二分图没有三角形,团最多包含两个顶点;一般图中的团搜索在这里退化为边或孤立点。

最小点着色

左右两侧直接使用两种颜色;只要图中存在边,最少需要两色。

最小边着色

二分图的边染色数等于最大度数 Δ,每一种颜色对应一组互不抢端点的匹配边。

最大匹配

一对一选择变成互不共享端点的边,可通过增广路在 O(|L|·|E|) 内求解。

最小边覆盖

没有孤立点时,可由最大匹配补边得到,数量为 |V|−最大匹配数。

最小点覆盖

Kőnig 定理保证二分图中最小点覆盖数等于最大匹配数。

最大独立集

独立集与点覆盖互为补集,因此二分图中可由最大匹配间接得到。

最大权匹配

边带收益后变成指派问题,可使用加权匹配算法或费用流。

二分图博弈

部分删点移动博弈可利用最大匹配中的关键点判断胜负。

返回主线当前关系图已经验证为二分图,可以研究“一对一选择”本身。

04 · 一对一选择

匹配与贪心方案

本章作用把“合法录取方案”正式表示成边集合,并检查只选眼前可用岗位的贪心策略。
主线知识

匹配边、非匹配边、匹配点与自由点;贪心为什么可能停在非最优答案。

扩展知识

最大匹配与完美匹配留到增广过程完成后再定义。

贪心先到先得

比完整枚举更直接的方案是逐人选择第一个尚未占用的岗位。先让阿青去前端、晨晨去后端后, 小北已经找不到可用岗位。蓝色边表示已经选中,灰色虚线依然合法,只是当前没有选择。

当前匹配 M先选阿青—前端、晨晨—后端

小北与测试岗位仍是自由点。

合法但未选择匹配边

一组被选边如果互不共享端点,就叫匹配。这里的两条蓝边没有抢同一个人, 也没有抢同一个岗位,所以它们构成一份大小为 2 的匹配。

匹配词汇

匹配边

蓝色边,属于当前方案 M。

非匹配边

灰色虚线,合法但当前没有选择。

匹配点

接触一条匹配边的点,如阿青、前端。

未匹配点

没有接触匹配边的自由点,如小北、测试。

痛点证据贪心得到 2,但枚举已经证明答案可以达到 3。

问题不在合法性检查,而在早期选择占用了后来更稀缺的岗位。只寻找空岗位无法修正旧决定。

剩余问题需要一种允许调整已有配对、但又不重新枚举全部方案的方法。

05 · 调整旧选择

交替路

本章作用解决贪心无法撤回早期选择的问题,把一连串替换关系组织成路径。
主线知识

非匹配边与匹配边为何必须交替,以及路径中每一步对应的实际操作。

扩展知识

一般交替路不要求端点自由;能够增加匹配数的特殊路径留到下一章。

小北只能去前端,而前端已经属于阿青。沿着前端的蓝边找到阿青,再检查阿青能否改去后端; 后端又属于晨晨,因此继续检查晨晨能否改去测试。测试恰好空闲。

1
小北 → 前端

走一条尚未选择的边。

2
前端 → 阿青

前端已占用,沿匹配边找到原搭档。

3
阿青 → 后端 → 晨晨

继续重复“尝试新边—找到旧搭档”。

4
晨晨 → 测试

抵达右侧自由点,整条替换链成立。

一条增广路小北 → 前端 → 阿青 → 后端 → 晨晨 → 测试

按步骤观察非匹配边与匹配边怎样交替出现。

初始状态五条合法边暂时全部显示为灰色

点击“下一步”,从左侧自由点小北出发。

尚未经过匹配边非匹配边
交替路

相邻两条边必须一条属于当前匹配 M、另一条不属于 M,不能连续经过两条匹配边或两条非匹配边。定义本身不要求端点自由。

增广路
  1. 本身是一条交替路。
  2. 两个端点都是未匹配点。
  3. 因此前后两端必然是非匹配边。
  4. 所以非匹配边恰好比匹配边多一条。

“非匹配边数量多一条”是前三个条件共同产生的结果,不能脱离交替性和自由端点单独判定。

更复杂的例子:八节点交替链

上面的替换链只有一条出路。当候选人拥有多个可选岗位时,交替搜索会产生分支。 看一个八个节点、十条边的例子:阿强同时能胜任前端、后端、测试、运维四个岗位, 但贪心先把前端、测试、后端分别给了阿强、小美、阿力。

当前匹配 M · 复杂例子先选阿强—前端、小美—测试、阿力—后端

小可与运维岗位仍是自由点;灰色虚线是合法但未选择的关系(如阿力—运维)。

合法但未选择匹配边

现在小可只剩“前端、后端”两个念头,而它们都已被占用;运维岗位空着, 但小可自己并没有直接通向运维的边——连向运维的阿力已经被后端占用, 只有阿强还握着这条自由出路。于是交替链变成:小可沿非匹配边到前端, 再沿匹配边回到阿强;阿强面前有四条出路,前三条都被占用,第四条运维是自由点。

1
小可 → 前端

走一条尚未选择的边,发现前端已被占用。

2
前端 → 阿强

沿当前匹配边找到原搭档阿强。

3
阿强 → 运维

阿强依次尝试四条出路,前三条被占用,第四条通向自由点。

一条更长的增广路小可 → 前端 → 阿强 → 运维

阿强有四条出路:前端、后端、测试都已被占用,第四条运维通向自由点。

初始状态十条合法边暂时全部显示为灰色

点击“下一步”,从左侧自由点小可出发。

尚未经过匹配边非匹配边
翻转之后小可—前端、阿强—运维、小美—测试、阿力—后端

没有任何端点被重复占用,匹配数从 3 变成 4。

合法但未选择匹配边
分支说明出路多不等于增广路多。

增广路每一步只取一条交替边:尝试被占用的岗位就沿匹配边回到原搭档,直到某个终点是自由点。阿强的前三条出路只是“先被尝试、再被排除”,路径本身始终只有一条,不需要记录整棵搜索树。

DFS 搜索:一条不同的增广路

真实搜索不会总是一帆风顺。假设阿强的邻接顺序是“前端、测试、后端、运维”, 那么从小可出发的 DFS 会先钻进一条死路:阿强先试测试,测试被小美占用, 而小美除了测试只剩前端(已访问),递归失败。DFS 于是回退到阿强, 继续尝试下一条出路:后端被阿力占用,再请阿力另找,阿力只剩运维且空闲, 整条链才成功。

1
小可 → 前端

非匹配边,前端被阿强占用。

2
前端 → 阿强

匹配边,请阿强让出前端。

3
阿强 → 测试

阿强按顺序先试测试,发现被小美占用。

4
测试 → 小美

匹配边,递归调用 tryMatch(小美),请小美另找位置。

5
小美 → 无路可走

小美只能去前端或测试:前端已访问、测试是来路,递归返回失败。

6
回退到阿强

测试分支整体失败,DFS 回溯到阿强,继续尝试下一条出路。

7
阿强 → 后端

阿强接着试后端,发现被阿力占用。

8
后端 → 阿力

匹配边,递归调用 tryMatch(阿力),请阿力另找位置。

9
阿力 → 运维

阿力只剩运维一条新出路,运维空闲,递归返回成功。

DFS 搜索轨迹 · 失败与回退小可 → 前端 → 阿强 → 测试 ✗ 回退 → 后端 → 阿力 → 运维

测试分支失败后回退到阿强,再走后端分支才成功。

初始状态十条合法边暂时全部显示为灰色

点击“下一步”,从左侧自由点小可出发。

尚未经过匹配边非匹配边失败分支
递归语义tryMatch(占用者) 就是“请原搭档另找位置”。

代码里的 if (matchRight[v] == -1 || tryMatch(matchRight[v])) 表示: 岗位空闲直接占用;否则递归请当前占用者另找。小美的递归返回 false 时, 阿强的“测试”分支被放弃,代码回到 for 循环尝试下一条边——这就是回溯。 直到某个分支递归成功,整条链让位,匹配数 +1。

分析结果交替结构能够描述旧配对的连锁替换,但只有特定端点条件才会增加答案。

06 · 增加一对

增广路与翻转

本章作用从交替路中筛出能够严格增加匹配数的路径,并验证翻转后仍是一份合法匹配。
主线知识

两个自由端点、非匹配边多一条、对称差翻转和匹配数增加 1。

扩展知识

Berge 定理用于判断当前匹配是否已经最大。

翻转操作

不能只把小北—前端加进去,那会让前端同时属于两个人。正确操作是把整条增广路上的边同时翻转: 原匹配边删除,原非匹配边加入。记作 M′ = M △ E(P)

翻转之后小北—前端、阿青—后端、晨晨—测试

没有任何端点被重复占用,匹配数从 2 变成 3。

合法但未选择匹配边
翻转前2

阿青—前端、晨晨—后端

2 条旧边移除
3 条新边加入
翻转后3

小北—前端、阿青—后端、晨晨—测试

Berge 定理:何时停止?

BERGE 定理
一个匹配是最大匹配,当且仅当不存在相对于它的增广路。

因此算法不需要猜最终答案:找到增广路就加 1;再也找不到时,当前匹配就是最大匹配。

优化效果无需推翻整份方案;一条增广路只改变路径上的局部配对,并使答案增加 1。

07 · 目标定义

最大匹配与完美匹配

本章作用增广过程已经说明匹配怎样增长,此处再区分“数量最大”和“覆盖所有点”两个目标。
主线知识

最大匹配、完美匹配及二者关系。

扩展知识

Hall 定理描述覆盖左侧全部点的充要条件。

Maximum

最大匹配

在所有匹配中,边数最多的一份。这个例子最多为 3。

Perfect

完美匹配

每一个顶点都恰好被匹配一次。翻转后的 3 对同时也是完美匹配。

“最大”只表示不能获得更多配对,不保证覆盖全部点。例如 4 位候选人竞争 3 个岗位时, 最大匹配最多为 3,但永远无法覆盖所有候选人。完美匹配一定是最大匹配,反过来不一定。

Hall 定理:什么时候能够覆盖左侧全部点?

对左侧任意点集 S,把它们能够连接的右侧点合起来记作 N(S)。存在覆盖左侧全部点的匹配, 当且仅当每个 S 都满足 |N(S)| ≥ |S|。直觉是:任意一群候选人可选择的岗位总数, 至少要和这群候选人一样多。

08 · 搜索结构

匈牙利树

本章作用一条替换链可以直接记录;当候选边发生分叉时,需要保存搜索父子关系。
主线知识

偶数层左点、奇数层右点,以及非匹配边和匹配边的层间移动规则。

扩展知识

搜索树只是原图的遍历记录,并不是新的业务关系图。

从小北开始的一连串“这个岗位有人吗—原搭档还能去哪”,就是一次交替搜索。 当一个人有多个可选岗位时,搜索会产生分支;记录每个点从谁而来,便展开成一棵交替搜索树, 也常称匈牙利树。

交替树的层次规则

偶数层 · 左侧候选人

沿尚未匹配的边尝试岗位。

非匹配边 →
奇数层 · 右侧岗位

若已占用,沿唯一匹配边找原搭档。

匹配边 →
下一偶数层原搭档

继续尝试他的其他岗位。

树不是另一张业务图。

原图仍是那 6 个点和 5 条边;树只是记录搜索次序与父节点。到达自由岗位后,根到叶的路径就是增广路。

09 · 算法实现

DFS 增广

本章作用把交替搜索和路径翻转写成可重复执行的程序,替代指数级完整枚举。
主线知识

matchRight、visited、递归改配和外层逐点增广。

扩展知识

更大稀疏图可使用 Hopcroft–Karp;容量大于 1 时改用网络流。

对每一个左侧点尝试一次:遇到空闲右点就直接匹配;遇到已占用右点,就递归请它的原搭档另找位置。 递归成功后,当前点才接管这个右点。这对应“小北占用前端、阿青改去后端、晨晨改去测试”的替换链。

核心递归

C++无权二分图最大匹配O(|L| · |E|)
vector<vector<int>> graph;
vector<int> matchRight; // 右侧岗位当前与谁匹配
vector<bool> visited;

bool tryMatch(int u) {
    for (int v : graph[u]) {
        if (visited[v]) continue;
        visited[v] = true;

        // 岗位空闲,或者原搭档能够换到别处
        if (matchRight[v] == -1 || tryMatch(matchRight[v])) {
            matchRight[v] = u;
            return true;
        }
    }
    return false;
}

int maximumMatching(int leftCount, int rightCount) {
    matchRight.assign(rightCount, -1);
    int answer = 0;
    for (int u = 0; u < leftCount; ++u) {
        visited.assign(rightCount, false);
        if (tryMatch(u)) ++answer;
    }
    return answer;
}
matchRight[v]

右侧点 v 当前的搭档,冲突时沿它找到原搭档。

visited[v]

一次寻找中不重复询问同一右点,防止递归绕圈。

tryMatch 成功

找到一条增广路,递归回程会完成整条路的翻转。

外层循环

每个左点都尝试让当前匹配增加 1。

名称边界

中文竞赛资料常把这套 DFS 增广法叫“匈牙利算法”,也常称 Kuhn algorithm。 Kuhn 1955 年论文中的 Hungarian Method 处理的是带权指派问题,并不是上面这段 DFS。

复杂度对比

完整枚举O((m+1)n)

重新尝试大量完整方案。

针对“组合爆炸”进行优化
DFS 增广O(|L|·|E|)

每轮只搜索一条局部替换路径。

10 · 进阶加速

BFS 增广与 Hopcroft–Karp

本章作用DFS 一次只找一条增广路;BFS 让所有自由左点一起扩散,一轮找出多条最短增广路。
主线知识

BFS 分层、分层图上 DFS、每轮增广多条;复杂度 O(|E|·√|V|)。

扩展知识

与网络流 Edmonds–Karp / Dinic 的“广度分层 + 深度找路”是同一对思想。

BFS 分层

在交替路里,搜索方向被结构锁死:非匹配边一定从左到右、匹配边一定从右到左。 BFS 利用这一点,把所有自由左点同时放进队列,按 “左点 → 非匹配边 → 右点 → 匹配边 → 左点”逐层扩散。 第一次到达某个未匹配右点的层数,就是当前最短增广路的长度。

偶数层 · 左侧自由左点起步

全部自由左点同时入队,距离 0。

非匹配边 →
奇数层 · 右侧岗位

未匹配即找到增广路;已占用则沿匹配边去下一层。

匹配边 →
下一偶数层原搭档

距离 +1,继续尝试他的其他岗位。

队列 QUEUE
ABC
dist
A0
B0
C0
foundfalse
BFS 分层当前执行行
01def bfs():
02 q = deque()
03 for u in left:
04 if match_l[u] == -1:
05 dist[u] = 0; q.append(u)
06 else:
07 dist[u] = -1
08 found = False
09 while q:
10 u = q.popleft()
11 for v in graph[u]:
12 pu = match_r[v]
13 if pu == -1:
14 found = True
15 elif dist[pu] == -1:
16 dist[pu] = dist[u] + 1
17 q.append(pu)
18 return found
1 / 14 步 · 空匹配:程序从这里开始

match_l、match_r 全部初始化为 -1,A、B、C 都是自由左点。BFS 的初始化循环把它们全部放入队列:dist[A] = dist[B] = dist[C] = 0。

分层图:BFS 增量构建

BFS 的产物就是一张分层图:自由左点在第 0 层, 沿“非匹配边 → 右点 → 匹配边 → 左点”每推进一轮,左点层数 +1。 右侧的空闲右点就是终点。下面的演示把这张图一棵树一样逐步长出来, 每一步只新增一个节点或一条边。

分层图 · 增量构建1 / 8 步 · 根:自由左点 C
第 0 层 · dist = 0右点 · 奇数层第 1 层 · dist = 1终点:空闲右点Cdist = 0
自由点(红圈)当前探索匹配边增广路终点 ✓
根:自由左点 C · 第 2 轮 BFS 从自由左点 C 出发,dist[C] = 0,C 入队。分层图以 C 为根。

DFS 搜索树:增量展开

拿到分层图后,DFS 只沿着“层数恰好 +1”的方向走,把尝试过程展开成一棵搜索树: 左点先选一条非匹配边到右点,右点被占用就递归到原搭档,原搭档再分出多条候选路, 失败的分支标 ✗,成功走到空闲右点标 ✓,整条路径最后一起翻转。

DFS 搜索树 · 增量展开1 / 6 步 · 根:自由左点 C
DFS 搜索树 · 从 C 开始C自由左点
自由点(红圈)当前探索匹配边增广路终点 ✓
根:自由左点 C · dfs(C) 开始:目标是找一条以自由左点 C 开头、自由右点结尾的增广路。

一轮的执行:BFS 分层 + DFS 沿层增广

Hopcroft–Karp 把一轮分成两半:BFS 只负责算距离(dist[u]), DFS 只沿着“距离恰好 +1”的方向走,因此每条找到的路都是最短增广路, 并且一轮内可以找出多条互不相交的最短增广路,一起翻转。

C++Hopcroft–Karp 无权二分图最大匹配O(|E|·√|V|)
const int INF = 1e9;
vector<int> g[MAXN];   // g[u]:左点 u 能匹配的右点列表(邻接表)
int matchL[MAXN];      // matchL[u]:左点 u 当前匹配到的右点,0 表示空闲
int matchR[MAXN];      // matchR[v]:右点 v 当前匹配到的左点,0 表示空闲
int distL[MAXN];       // distL[u]:BFS 分层中左点 u 的层数,INF 表示未访问

bool bfs() {
  queue<int> q;
  for (int u = 1; u <= nL; ++u) {
    if (matchL[u] == 0) distL[u] = 0, q.push(u);   // 所有自由左点作为第 0 层
    else distL[u] = INF;
  }
  distL[0] = INF; // 0 号是虚拟空闲右点:所有空闲右点都指向它
  while (!q.empty()) {
    int u = q.front(); q.pop();
    for (int v : g[u])
      // 右点 v 空闲(matchR[v] == 0)时走到虚拟点;否则沿匹配边走回左点 matchR[v]
      if (distL[matchR[v]] == INF) {
        distL[matchR[v]] = distL[u] + 1;
        q.push(matchR[v]);
      }
  }
  return distL[0] != INF; // 能到达虚拟空闲右点 -> 存在增广路
}

bool dfs(int u) {
  if (u == 0) return true; // 走到虚拟空闲右点,增广路终点已找到
  for (int v : g[u])
    if (distL[matchR[v]] == distL[u] + 1 && dfs(matchR[v])) {
      matchR[v] = u; // 右点 v 改配左点 u
      matchL[u] = v; // 左点 u 匹配右点 v
      return true;
    }
  distL[u] = INF; // 剪枝:u 已无路可走
  return false;
}

int hopcroftKarp() {
  int ans = 0;
  while (bfs())
    for (int u = 1; u <= nL; ++u)
      if (matchL[u] == 0 && dfs(u)) ++ans; // 只从自由左点出发增广
  return ans;
}

DFS 与 BFS 的对比

维度DFS(Kuhn 匈牙利)BFS(Hopcroft–Karp)
搜索策略一条路钻到底,失败回退所有自由左点逐层扩散
找到的路径任意一条增广路最短增广路
每轮增广数1 条多条互不相交
复杂度O(|L|·|E|)O(|E|·√|V|)
适用场景小规模、模板题大规模稀疏图
为什么 BFS 更快?

每完成一轮,最短增广路长度严格增加;路径长度最多到 O(√|V|) 就结束了, 所以总轮数有界,复杂度降到 O(|E|√|V|)。这与网络流里 Edmonds–Karp (BFS 找单条最短路)和 Dinic(BFS 分层 + DFS 阻塞流)是同一对思想。

选型建议小数据用 DFS 简洁;数据达到 10^5 级别,改用 Hopcroft–Karp。

11 · 模型迁移

四类二分匹配建模

本章作用主问题已经由 DFS 增广解决,此处检查相同结构在其他题目中怎样被识别。
主线知识

两类资源、合法关系、每个资源至多使用一次,以及最大/完美匹配目标。

扩展知识

不同题面只改变点和边的含义,不改变匹配算法。

统一判断句

一次选择是否恰好占用左侧一个资源和右侧一个资源,并且每个资源最多使用一次?

01

男女配对

左右是男孩与女孩,彼此喜欢就连边。最多组成多少对是最大匹配;能否让所有人配对是完美匹配。

02

候选人与岗位

本文贯穿例子。候选人与岗位各最多使用一次,一次录取就是一条匹配边。

03

有墙棋盘放棋子

墙把行列切成横向段与纵向段。一个空格连接它所在的横段和纵段;在格子放棋子就是选择这条边。

04

多米诺覆盖

棋盘黑白染色,相邻格异色。一块骨牌连接一个黑格与一个白格;覆盖全部格子就是完美匹配。

有墙棋盘转化成横段纵段匹配
有墙棋盘:空格是横段与纵段之间的合法关系,棋子才是最终选中的匹配边。
棋盘黑白染色后的多米诺匹配
多米诺:每块骨牌占用相邻的一黑一白两个格点。

12 · 判断边界

匹配问题的识别顺序

01
先找对象和关系

谁是点?什么条件产生边?

02
确认能否分成两组

每条边是否只跨组,不留在组内?

03
把一次选择看成边

两条选择是否不能共享端点?

04
从自由点寻找替换链

交替经过非匹配边与匹配边。

05
到达自由点后翻转

每找到一条增广路,答案增加 1。

每个点容量大于 1考虑 b-matching 或最大流。

边有收益或代价考虑带权匹配、指派算法或费用流。

图无法二染色考虑一般图匹配,不能直接套本文 DFS。

13 · 参考

继续阅读