图的匹配算法及其相关

图的匹配算法及其相关

本文大量参考了:

  • 国家集训队2015论文集,陈胤伯,浅谈图的匹配算法及其应用
  • 国家集训队2017论文集,杨家齐,基于线性代数的一般图匹配
  • Fuyuki 的博客,题解 P6113 【模板】一般图最大匹配

匹配和增广路

对于任意图 G = ( V , E ) G=(V,E) G=(V,E),一组两两没有公共交点的边集 M M M M ⊆ E M\subseteq E ME)称为这张图的一个匹配。

把在匹配中的点称为匹配点,把不在匹配中的点称为未盖点。所有点都是匹配点的匹配被称作完美匹配。

P P P 为图中一条简单路径,满足其两个端点都是未盖点,且 P P P 中的边为非匹配边、匹配边交替出现(满足这种条件的路径我们也称为交替路),则称 P P P 为一条增广路。

  • 定理 1:一个匹配 M M M 是图 G G G 的最大匹配当且仅当 G G G 中不存在增广路。

    证明:必要性显然,考虑充分性。

    考虑 M M M 和一个存在的更大的匹配 M ′ M' M 的对称差 D = M ⊕ M ′ = ( M ∪ M ′ ) − ( M ∩ M ′ ) D=M\oplus M'=(M\cup M')-(M\cap M') D=MM=(MM)(MM)

    容易知道 D D D 中来自 M ′ M' M 的元素个数仍然大于来自 M M M 的元素个数。

    D D D 中每个点度数至多为 2 2 2,且度数为 2 2 2 的点两条邻边分别来自 M M M M ′ M' M。这说明 D D D 肯定由若干简单环和链组成,且在环和链中来自 M M M M ′ M' M 的边肯定交替出现,由此可知 D D D 中的环都是简单偶环。

    注意对称差中 “交替” 的含义和原图中 “交替” 的含义不同,对称差中指的是来自 M M M M ′ M' M 的边交替出现,原图中指的是匹配边和非匹配边交替出现。

    考虑去掉那些简单环,剩下的图中肯定仍然有来自 M ′ M' M 的边的数量大于来自 M M M 的边的数量。

    于是肯定存在一条两端的边都来自 M ′ M' M 的链,这对于 M M M 来说是一条增广路,于是充分性得证。

由定理 1 的证明可以看出:设 M , M ′ M,M' M,M 是两个不同的最大匹配,设 D D D 为它们的对称差, D D D 仍然由若干偶环和若干链(此时一定为偶链)组成。这说明任意两个最大匹配,都可以通过对一个中的若干不相交偶环、偶链取反得到另一个。特别地,两个完美匹配的对称差只有偶环,否则如果有偶链肯定存在未盖点。

从定理 1,我们可以得到一个求最大匹配的算法:每次遍历所有未盖点找增广路并增广,直到找不到为止。(注意增广一次最大匹配一定会增大 1 1 1,所以算法的时间复杂度是有限的)

  • 引理 1:如果某一次找不到从未盖点 x x x 出发的增广路,那么无论如何再增广也依然找不到从未盖点 x x x 出发的增广路。

    证明:考虑某一次对于增广路 a − b a - b ab 增广后,出现了以 x x x 为端点的增广路 P x P_x Px,则 P x P_x Px 一定和 a − b a-b ab 有交,而由于增广前 a − b a-b ab 是交错路(交点在 a − b a-b ab 上的两条邻边必然有一条与在 P x P_x Px 上的那条邻边匹配性互异)且 a , b , x a,b,x a,b,x 均为未盖点,所以增广前 x x x 就已经能够走到 a , b a,b a,b 中的某个点形成增广路了,矛盾。

另一方面,注意到增广一次实际上是对匹配点点集的扩充,即一个点如果已经是匹配点了那么之后也一定一直会是匹配点,所以我们可以对算法进一步优化:枚举每个未盖点,各找一次从它出发的增广路并增广(注意到增广一次后这个点就不可再增广了,所以只用找一次)。

有关二分图匹配
匈牙利算法

考虑把我们上一节所述的算法放到二分图中。当增广路的起点固定后,我们可以给每条边定向,然后把 “存在到未盖点 y y y 的增广路” 转化为 “沿着边的方向存在一条简单路径到达 y y y”。于是可以通过 dfs 找增广路。

每次找增广路的时间复杂度是 O ( m ) O(m) O(m) 的,于是总时间复杂度为 O ( n m ) O(nm) O(nm)

事实上,我们可以将复杂度降至 O ( 匹配数 × m ) O(\text{匹配数}\times m) O(匹配数×m):每次 dfs 若没有找到增广路,那么我们就不清空 vis 数组,因为从该次增广的起点 s s s 出发不能走到任何未盖点,那么从 s s s 能到达的任何点出发都不可能走到未盖点,于是在边的方向不变的情况下(未进行新的增广的情况下),已经遍历过的点就不用再遍历一次了。

二分图最大匹配关键点

关键点指的是一定在最大匹配中的点。

对于单个点,一种判定方法是先求出原图的最大匹配,然后再把这个点去掉求最大匹配,然后看最大匹配有没有变化。

另一种求出每一个关键点的方法:

先求任意一个最大匹配 M M M,那么关键点至少是现在图上的匹配点。考虑 M M M 中的一个匹配点 p p p,设 M ′ M' M 为某个不包含 p p p 的最大匹配,那么在它们的对称差 D D D 中一定存在一条以 p p p 为端点的偶交替链,这一条链的另一端在 M ′ M' M 中而不在 M M M 中,那么在原图中也一定存在一条以 p p p 为端点从匹配边出发的偶交替链,使得终点是某个未盖点 t t t

而且发现如果我们在原图中找到了这么一条偶交替链,那么我们可以通过将这条链取反的方式使得 p p p 不在匹配中。于是有:

  • 引理 2:一个匹配点 p p p 不是关键点,当且仅当存在一条以 p p p 为端点从匹配边出发的交替链,使得终点为某个未盖点 t t t(那么显然链长肯定为偶数)。

由于链长为偶数,所以 p , t p,t p,t 同侧,于是我们可以从左边每一个未盖点出发走交替链并给能到达的点打上标记,那么左侧没有标记的匹配点就是关键点。右边同理。分左右两侧处理的原因是 dfs 时我们可以通过当前点在左右哪一侧来判断下一条边是走匹配边还是非匹配边(相当于给每一条边定了个向),方便了记忆化,时间复杂度为 O ( n + m ) O(n+m) O(n+m)

二分图最小点覆盖

最大匹配 = 最小点覆盖

证明:

对于一个二分图,假设我们已经求出了它的最大匹配。

首先由于最大匹配中已经有了这么多条顶点互不相交的边了,所以最小点覆盖至少为最大匹配。

我们来通过构造证明它可以达到这个下界。

从左边每一个未匹配点出发走增广路(未匹配边、匹配边、……、未匹配边、匹配边),并给经过的点打上可达标记,接下来证明这组点集就是一组合法的最小点覆盖:左边所有不可达点+右边所有可达点。

先证明这是一组点覆盖,考虑所有的边 ( u , v ) (u,v) (u,v):( u u u 在左边, v v v 在右边)

  • ( u , v ) (u,v) (u,v) 为非匹配边,则若 u u u 可达,则 v v v 也可达。
  • ( u , v ) (u,v) (u,v) 为匹配边,则 u , v u,v u,v 可达状态是捆绑的,因为若可达 v v v 则可达 u u u,且不可能不通过 v v v 到达 u u u(可以注意到除了起点外,增广路上任何一个左侧点都是由其匹配边所对应的右侧点到达的)

所以不存在 u u u 可达、 v v v 不可达的边,所以我们只需要取出左边的不可达点和右边的可达点即可覆盖所有的边。

接下来还需说明这组点覆盖的大小恰为最大匹配:

注意到左边不可达点一定是匹配点(因为我们从左边所有非匹配点都开始走了一遍),且右边可达点也一定是匹配点(否则存在增广路),而由上面边 ( u , v ) (u,v) (u,v) 为匹配边时 u , v u,v u,v 可达状态捆绑可知一条匹配边只有恰好一个点在点覆盖内,所以这组点覆盖的大小恰为最大匹配。

另一种证明方式:二分图上最大流(最大匹配)= 最小割(最小点覆盖)。

二分图最大独立集

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

证明:

把最大独立集看作是:在原图上每次删掉一个点及其相连的边,求删去最少的点使得原图所有边都被删完。于是最大独立集即为最小点覆盖的补集。

二分图最小边覆盖

最小边覆盖 = n - 最大匹配

证明:

只会构造方式:先贪心地选一组最大匹配,然后剩下的未匹配点各自选一条出边。总边数 = 最大匹配 + (n - 最大匹配 * 2) = n - 最大匹配。

事实上这种构造方式对于任意图都成立。

Hall 定理

对于一个二分图 G = ( A , B ) G=(A,B) G=(A,B),设 f ( S ) f(S) f(S) 为与点集 S S S 相连的所有点组成的点集。

那么 A A A 中的点全部被匹配当且仅当:对于任意一个 A A A 的子集 S S S,均有 ∣ f ( S ) ∣ ≥ ∣ S ∣ |f(S)|\geq |S| f(S)S

更强地, G G G 的最大匹配为 ∣ A ∣ − max ⁡ S ⊆ A ( ∣ S ∣ − ∣ f ( S ) ∣ ) |A|-\max\limits_{S\subseteq A}(|S|-|f(S)|) ASAmax(Sf(S))

证明:只证更强的那个结论。记 M = max ⁡ S ⊆ A ( ∣ S ∣ − ∣ f ( S ) ∣ ) M=\max\limits_{S\subseteq A}(|S|-|f(S)|) M=SAmax(Sf(S))

首先,显然最大匹配不超过 ∣ A ∣ − M |A|-M AM。我们只需证明存在一个大小为 ∣ A ∣ − M |A|-M AM 的匹配即可。

假设不存在,那么 A A A 中至少存在 M + 1 M+1 M+1 个非匹配点,任取其中的 M + 1 M+1 M+1 个,记为 S S S。再设 T T T 初始为空集。

归纳地假设 S S S 中每个点 u u u 都存在一条半增广路(从某个左部非匹配点开始,走交错路,一直到达 u u u),显然初始时成立。归纳地假设 ∣ S ∣ = M + k |S|=M+k S=M+k ∣ T ∣ = k − 1 |T|=k-1 T=k1(初始时 k = 1 k=1 k=1)。归纳地假设 T T T 中都是匹配点,且 S S S 中要么是初始的 M + 1 M+1 M+1 个非匹配点,要么是 T T T 中点的匹配点。

根据 M M M 的定义可知 ∣ f ( S ) ∣ ≥ k |f(S)|\geq k f(S)k。于是可以取 v ∈ f ( S ) v\in f(S) vf(S) v ∉ T v\not\in T vT,那么存在 u ∈ S u\in S uS 使得存在边 ( u , v ) (u,v) (u,v)

v v v 为非匹配点,根据归纳假设,我们找到了一条先到 u u u 再到 v v v 的增广路,于是有更大的匹配。

v v v 为匹配点,将 v v v 加入 T T T 中,再将 m a t c h ( v ) match(v) match(v) 加入 S S S 中。

容易证明上述过程一直符合归纳假设,而该过程不可能无限次进行,即一定在某次找到更大的匹配。

k-正则图二分图染色

对于一个 k-正则二分图,最少能够用 k k k 种不同的颜色将所有边染色,并使得不存在一个点连出两条同色的边。

必要性显然,充分性只需要证明如下引理即可归纳:

对于一个 k-正则二分图,一定存在一组完美匹配。

证明:考虑使用 Hall 定理证明,考虑左部的任意一个点集 S S S,它们会连出 k ∣ S ∣ k|S| kS 条边,而右部的任意一个点的最大度数都为 k k k,所以 T o ( S ) To(S) To(S) 大小至少为 ∣ S ∣ |S| S,得证。

构造方法:

显然每一次求一遍最大匹配的时间复杂度是不优的,我们有一种更加优秀的类似匈牙利的做法:

考虑增量法,每次加入一条边 ( u , v ) (u,v) (u,v) 时(不妨设 u u u 是左部点, v v v 是右部点),我们找到当前 u , v u,v u,v 各自出边颜色集合的 mex ⁡ \operatorname{mex} mex,分别记为 L , R L,R L,R。然后我们从 v v v 开始走一条极长的 L , R , L , R , ⋯ L,R,L,R,\cdots L,R,L,R, 的增广路,容易发现过程中一定不会出现环:首先路径上除了 u , v u,v u,v 之外的点都有 L , R L,R L,R 两种出边,而之前的图我们已经保证了染色是合法的了,所以成环的那条边不可能接在除了 u , v u,v u,v 之外的点上;而对于 u , v u,v u,v,由于二分图的性质,当前下一步若要走 L L L 则一定连向左部点,若要走 R R R 则一定连向右部点,而我们又保证了 L , R L,R L,R 没在 u , v u,v u,v 各自的出边颜色集合中出现过,所以成环的那条边也不可能接在 u , v u,v u,v 上。

找到增广路后,我们直接把这条增广路整体 L , R L,R L,R 翻转,并把 ( u , v ) (u,v) (u,v) 的颜色设为 L L L 即可。

每次都是从 u , v u,v u,v 出边的颜色集合中取 mex ⁡ \operatorname{mex} mex 的原因是使得每一个点的出边颜色肯定是 0 ∼ k − 1 0\sim k-1 0k1,符合 k k k 染色条件。

由于增广路无环,所以增广路的长度是有限 O ( n ) O(n) O(n) 的,所以这个算法时间复杂度为 O ( n m ) O(nm) O(nm) 的。

另外提一嘴,这个算法适用于一般的点最大度数为 k k k 的二分图的 k k k 染色,即无需把图补全为正则图,那样复杂度会退化到 O ( n 2 k ) O(n^2k) O(n2k)

代码实现也很简单:

int to[N][K];
void dfs(int u,int c1,int c2)
{
    if(to[u][c1]) dfs(to[u][c1],c2,c1);
    swap(to[u][c1],to[u][c2]);
}
void inse(int u,int v)
{
    int cu=0,cv=0;
    while(to[u][cu]) cu++;
    while(to[v][cv]) cv++;
    dfs(v,cu,cv);
    to[v][cu]=u,to[u][cu]=v;
}
其他
  • 给定二分图 ( A , B , E ) (A,B,E) (A,B,E),其中 A , B A,B A,B 是两部点集, E E E 是边集。对于 B B B 中的每个点都给定一个点权。定义该二分图上的一个匹配的代价为所有 B B B 中的匹配点的点权之和。求在匹配最大的情况下,匹配代价最小可能是多少。

    做法:将 B B B 中的点按点权从小到大排序,并以这个顺序跑匈牙利算法(按序依次增广),最后跑出来的匹配就是代价最小的最大匹配 M M M

    证明:考虑另一个代价更小的最大匹配 M ′ M' M,并考虑 M M M M ′ M' M 的对称差,它由若干偶环和偶链组成。注意到偶环异或之后匹配点不变(不管是 M M M 还是 M ′ M' M,整个环都是匹配点);而对于偶链来说,从 M M M 变成 M ′ M' M,点集的变化相当于去掉了链的一头 a a a,再加入链的另外一头 b b b。由于 M ′ M' M 的代价比 M M M 小,那么必然存在某条链满足 a , b ∈ B a,b\in B a,bB w b < w a w_b<w_a wb<wa,此时 b b b 的尝试增广时间比 a a a 早。考虑最后一次修改这条链上的边的匹配状态的时候(此时一定已经尝试过从 b b b 增广了),显然是对该路径的若干段取反,且每一段的两端在取反后都是匹配边,即在取反前是非匹配边。找到离 b b b 最近的那一段,发现可以找到一条从 b b b 开始的增广路,这和引理 1 矛盾。

  • 对于二分图 G G G。设有两组 G G G 上的最大匹配 M 1 , M 2 M_1,M_2 M1,M2,其中 M 1 M_1 M1 的左侧匹配点集为 L L L M 2 M_2 M2 的右侧匹配点集为 R R R。那么存在一组最大匹配,其匹配点集恰为 L ∪ R L\cup R LR

    证明:考虑从 M 1 M_1 M1 调整到 M 2 M_2 M2。考虑 M 1 , M 2 M_1,M_2 M1,M2 的对称差。偶环我们可以忽略。对于偶链,我们可以把链的一端换成另一端,注意到这两端一定同在左部(右部),所以一定不可能同时属于 L L L(同时属于 R R R),于是根据需要调整即可。

    应用:和匈牙利算法结合,求二分图最大点权匹配,或构造钦定匹配点的匹配等。

一般图最大匹配——带花树算法

对于一般图最大匹配,我们不能直接套用二分图最大匹配的方法,因为奇环的存在,所以到达一个点时接下来走的边不是确定的。

处理的办法是把奇环消掉:对于一个大小为 2 k + 1 2k+1 2k+1 的奇环,其内如果已经存在了 k k k 对匹配边(达到上界),那么一定存在一个恰好一个节点能向外匹配。同时,通过环内增广,每个节点都可以成为这个向外匹配的点。

于是此时这个奇环和一个点实质上是等价的,我们可以把这个奇环缩成一个点(称为花)。

具体地:

假设当前枚举的起点 s s s(未盖点)已经确定了,不妨先当成二分图来做,从 s s s 以非匹配边开始 bfs 找增广路。

我们对走出来的这棵交错树中的点按层数奇偶性分成奇点偶点,其中根为偶点。那么树上偶点向奇点的连边一定是非匹配边,奇点向偶点的连边一定是匹配边。

不难发现每次偶点都是直接被奇点直接沿着匹配边得到(相当于二者已经捆绑),于是我们只考虑枚举偶点的出边的情况。

设当前点为 u u u(偶点),枚举非匹配边 ( u , v ) (u,v) (u,v)

  • v v v 未被访问过:若 v v v 是未盖点则找到增广路;否则将 v v v 的配偶加入队列中。
  • v v v 被访问过:那么我们找到了一个环。如果是偶环则直接忽略,否则若是奇环,则把环上所有边缩掉缩成一个点并继续找增广路。

意思就是从 s s s 开始找增广路,若遇到增广路就直接增广,否则若遇到奇环就把它缩成一个点并把这个新点重新加入队列。最后若没有奇环(已经变成二分图了)且也找不到增广路直接换到下一个起点即可(需要把图清空,因为当前起点缩的奇环对下一个点不一定成立)。

考虑一下这个算法的正确性,我们只需要证明我们找到的奇环和一个点是等价的,即设原图为 G G G,缩完点后的图为 G ′ G' G,我们只需要证明:

  • G G G 中存在增广路, G ′ G' G 中也存在增广路。
  • G ′ G' G 中存在增广路, G G G 中也存在增广路。

借用一张论文中的图:

在这里插入图片描述

非树边为 ( u , v ) (u,v) (u,v),定义花根 h h h u , v u,v u,v 的最近公共祖先。

由此环是奇环可知 u , v u,v u,v 深度奇偶性相同,所以 v v v 也是偶点。而且若 u , v u,v u,v 不呈祖先关系那么 u , v u,v u,v 仅有可能是在树上某个偶点 h h h 处沿着两条不同的非匹配边分开的。于是 h h h 一定是偶点,交替路 s − h s-h sh 形如 “非匹配边-……-匹配边”。

  • G G G 中存在增广路, G ′ G' G 中也存在增广路。

    证明:考虑一条 G G G 中从 s s s 出发的增广路 P P P,如果我们能通过恰当的构造方式使得 P P P 能转化为 G G G 中一条同样从 s s s 出发、且每次进出奇环的边的匹配性都不同(一条是 h h h 的返祖边(匹配边),一条是奇环上的点向外连的非匹配边)的增广路,那 P P P G ′ G' G 中就肯定能唯一对应一条增广路。

    具体地,从前往后截取 P P P 中经过该奇环的每一段并分别考虑,假设当前截取的段是从 v i n v_{in} vin 进入奇环,从 v o u t v_{out} vout 离开奇环,即当前 P P P 形如 s − v i n − v o u t − ⋯ s- v_{in}- v_{out}-\cdots svinvout

    • 若从 v o u t v_{out} vout 离开奇环时是从某条非匹配边离开的,那么我们直接把路径改写成 s − h − v o u t − ⋯ s-h-v_{out}-\cdots shvout,其中 s − h s-h sh 段是沿树边走, h − v o u t h-v_{out} hvout 段是沿奇环走(顺时针走/逆时针走中总有一种合法),这条增广路仍然合法,而且进入奇环时是匹配边,离开奇环时是非匹配边。
    • 若从 v o u t v_{out} vout 离开奇环时是从匹配边离开的,那么只可能从 h h h 返祖的那条匹配边离开,此时一定是从某条非匹配边进入奇环,已经满足条件了。
  • G ′ G' G 中存在增广路, G G G 中也存在增广路。

    证明:比较显然,在 G ′ G' G 中经过奇环缩成的点时总有一种方法(顺时针/逆时针)在 G G G 中沿奇环复原。

在找到奇环的时候,为了实现方便,我们不 “显式” 地缩点,而是用并查集来记录每个点所在的花的根。而且因为缩起来的点是一个偶点,所以我们需要将环上所有的奇点都改成偶点加入队列中。

为了方便地遍历我们找到的增广路,对每个点存一个前驱 pre 表示这个点出发走一条非匹配边后应该到达哪个节点,即增广路上的上一个点。被缩掉的环上除了 h h h 连出的两条非匹配边之外的非匹配边的 pre 应该都是双向的(花内部的走法),在缩环时处理一下。

一个粗略的时间复杂度上界:处理一个奇环的时间是和奇环大小成线性的,而对于单个 s s s,一个点至多被缩 O ( n ) O(n) O(n) 次奇环,于是对 s s s 找增广路的时间是 O ( n 2 ) O(n^2) O(n2)。而一共要找 O ( n ) O(n) O(n) 次增广路,所以总时间复杂度为 O ( n 3 ) O(n^3) O(n3)

#include<bits/stdc++.h>

#define N 510
#define M 125000

using namespace std;

inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^'0');
        ch=getchar();
    }
    return x*f;
}

int n,m;
int cnt,head[N],nxt[M<<1],to[M<<1];
int fa[N],dep[N],pre[N],mat[N];
int ntim,tim[N];

void adde(int u,int v)
{
    to[++cnt]=v;
    nxt[cnt]=head[u];
    head[u]=cnt;
}

int find(int x)
{
    return x==fa[x]?x:(fa[x]=find(fa[x]));
}

int getlca(int u,int v)
{
    ntim++;
    while(u)
    {
        tim[u=find(u)]=ntim;
        u=pre[mat[u]];
    }
    while(1)
    {
        if(tim[v=find(v)]==ntim) return v;
        v=pre[mat[v]];
    }
}

queue<int>q;

void update(int u,int v,int h)
{
    while(find(u)!=h)
    {
        pre[u]=v;
        v=mat[u],fa[u]=fa[v]=h,u=pre[v];
        if(dep[v]==1) dep[v]=2,q.push(v);
    }
}

int flower(int st)
{
    while(!q.empty()) q.pop();
    for(int i=1;i<=n;i++) dep[i]=pre[i]=0,fa[i]=i;
    q.push(st),dep[st]=2;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int i=head[u];i;i=nxt[i])
        {
            int v=to[i];
            if(!dep[v])
            {
                pre[v]=u;
                if(!mat[v])
                {
                    int now=v;
                    while(now)
                    {
                        int y=mat[pre[now]];
                        mat[now]=pre[now];
                        mat[pre[now]]=now;
                        now=y;
                    }
                    return 1;
                }
                else
                {
                    dep[v]=1,dep[mat[v]]=2;
                    q.push(mat[v]);
                }
            }
            else if(dep[v]==2)
            {
                int h=getlca(u,v);
                update(u,v,h),update(v,u,h);
            }
        }
    }
    return 0;
}

int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        int u=read(),v=read();
        adde(u,v),adde(v,u);
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        if(!mat[i]) ans+=flower(i);
    printf("%d\n",ans);
    for(int i=1;i<=n;i++)
        printf("%d ",mat[i]);
    return 0;
}
一般图最大匹配——线性代数法
前置知识

首先是一些有关排列的符号和知识:

对于一个长度为 n n n 的排列 p p p,它也代表着一个置换,这个置换可以看成由若干个环组成(连有向边 i → p i i\to p_i ipi)。

我们记置换 p p p 的符号为 sgn ⁡ ( p ) \operatorname{sgn}(p) sgn(p),它有两种定义:

  • 排列 p p p 中的逆序对个数的奇偶性。
  • 从排列 1 , 2 , ⋯   , n 1,2,\cdots,n 1,2,,n 通过若干次交换相邻两项的操作得到排列 p p p 所需的操作次数的奇偶性。

两种定义是等价的:初始时排列 1 , 2 , ⋯   , n 1,2,\cdots,n 1,2,,n 的逆序对个数为 0 0 0,而每次交换相邻两个数会使得逆序对个数奇偶性改变。

声明一下,你可以直接看最后的引理 5,因为引理 2~4 是我姿势水平不够时写的,现在看来有一个更强的结论(引理 5)。

  • 引理 2:事实上第二种定义还有一个更强的推论—— sgn ⁡ ( p ) \operatorname{sgn}(p) sgn(p) 的第三种定义:从排列 1 , 2 , ⋯   , n 1,2,\cdots,n 1,2,,n 通过若干次交换任意不同的两项的操作得到排列 p p p 所需的操作次数的奇偶性。

    证明:假设我们要交换 p i , p j ( i ≠ j ) p_i,p_j(i\neq j) pi,pj(i=j),我们可以通过多次交换相邻两项的操作来实现,发现所需操作数和 2 ∣ i − j ∣ − 1 2|i-j|-1 2∣ij1 的奇偶性相同,即交换任意不同的两项等价于奇数次交换相邻的两项。

  • 引理 3:对于置换 a , b a,b a,b,有 sgn ⁡ ( a ∘ b ) = sgn ⁡ ( a ) ⊕ sgn ⁡ ( b ) \operatorname{sgn}(a \circ b)=\operatorname{sgn}(a)\oplus \operatorname{sgn}(b) sgn(ab)=sgn(a)sgn(b)

    证明:根据第二种或第三种定义,我们可以先通过操作将排列 1 , 2 , ⋯   , n 1,2,\cdots,n 1,2,,n 变到 a a a(操作次数奇偶性为 sgn ⁡ ( a ) \operatorname{sgn}(a) sgn(a)),再变到 a ∘ b a\circ b ab(操作次数奇偶性为 sgn ⁡ ( b ) \operatorname{sgn}(b) sgn(b)),总操作次数奇偶性为 sgn ⁡ ( a ) ⊕ sgn ⁡ ( b ) \operatorname{sgn}(a)\oplus \operatorname{sgn}(b) sgn(a)sgn(b)

  • 引理 4:对于置换 p p p,它可以看成由若干个有向环组成,将其中某一个环的边全部反向, sgn ⁡ ( p ) \operatorname{sgn}(p) sgn(p) 不变。

    证明:根据引理 3 3 3 sgn ⁡ ( p ) \operatorname{sgn}(p) sgn(p) 等于 p p p 中每个环的 sgn ⁡ \operatorname{sgn} sgn 异或起来,所以我们只需要关心这个环的 sgn ⁡ \operatorname{sgn} sgn 值有没有变化。

    此时需要再用回 sgn ⁡ \operatorname{sgn} sgn 的第一种定义。考虑这个环其实在排列中代表了若干个位置,记它们的集合为 I I I。那么这个环的 sgn ⁡ \operatorname{sgn} sgn 相当于是二元组集合 { ( i , a i ) ∣ i ∈ I } \{(i,a_i)|i\in I\} {(i,ai)iI} 中的逆序对个数的奇偶性。而将环反向后,原来的边 i → a i i\to a_i iai 变为了边 a i → i a_i\to i aii,也就是说二元组集合变成了 { ( a i , i ) ∣ i ∈ I } \{(a_i,i)|i\in I\} {(ai,i)iI},很显然这个二元组集合的逆序对个数和原来的二元组集合的逆序对个数是相同的,所以这个环反向后 sgn ⁡ \operatorname{sgn} sgn 依然没有变化。

  • 引理 5:对于置换 p p p,它可以看成由若干个有向环组成, sgn ⁡ ( p ) \operatorname{sgn}(p) sgn(p) 等于 n − 环个数 n-\text{环个数} n环个数 的奇偶性。

    证明:发现 sgn ⁡ ( p ) \operatorname{sgn}(p) sgn(p) 的第二种定义一次交换一定会改变环的奇偶性(断环或连成环),而初始时有 n n n 个自环,然后就易证了。

然后是一些有关线性代数的符号和知识:对于一个 m m m n n n 列( m × n m\times n m×n)的矩阵 A A A 以及任意 I ⊆ { 1 , ⋯   , m } , J ⊆ { 1 , ⋯   , n } I\subseteq\{1,\cdots,m\},J\subseteq\{1,\cdots,n\} I{1,,m},J{1,,n},我们记 A A A 中保留 I I I 中的行和 J J J 中的列得到的子矩阵为 A I , J A_{I,J} AI,J,而记 A A A 中删去 I I I 中的行和 J J J 中的列得到的子矩阵为 A I , J A^{I,J} AI,J。记 rank ⁡ A \operatorname{rank} A rankA 为矩阵的秩。

Tutte 矩阵和 Tutte 定理

对于一个无向图 G = ( V , E ) G=(V,E) G=(V,E),定义 G G G 的 Tutte 矩阵为一个 n × n n\times n n×n 的矩阵 A ~ ( G ) \tilde{A}(G) A~(G), 其中:

A ~ ( G ) i , j = { x i , j if  ( i , j ) ∈ E ∧ i < j − x i , j if  ( i , j ) ∈ E ∧ i > j 0 otherwise \tilde{A}(G)_{i,j}= \begin{cases} x_{i,j}&\text{if }(i,j)\in E \land i<j\\ -x_{i,j}&\text{if }(i,j)\in E \land i>j\\ 0&\text{otherwise} \end{cases} A~(G)i,j= xi,jxi,j0if (i,j)Ei<jif (i,j)Ei>jotherwise

其中 x i , j x_{i,j} xi,j ∣ E ∣ |E| E 个独立的变量。

定义图 G G G 的一个环覆盖是指选出 E E E 中的一些边组成若干个不交的简单环(可以是单边形成二元环)去覆盖 G G G 中所有的点。形式化地, 图 G G G 的每个环覆盖对应了 1 ∼ n 1\sim n 1n 的一个排列 π \pi π,满足 ( i , π i ) ∈ E (i,\pi_i)\in E (i,πi)E

如果这个环覆盖中的所有环的长度都是偶数,那么我们称这是一个偶环覆盖,否则我们称其是一个奇环覆盖。

  • 引理 5:图 G G G 有完美匹配当且仅当图 G G G 有一个偶环覆盖。

    证明:必要性:那么我们直接用完美匹配中每一对点构成的二元环就可以覆盖图 G G G 了。

    充分性:我们在每一个偶环中隔一条边取一条边,就能够得到 G G G 的一个完美匹配了。

  • 定理 2(Tutte 定理):图 G G G 有完美匹配当且仅当 det ⁡ A ~ ( G ) ≠ 0 \det \tilde{A}(G)\neq 0 detA~(G)=0

    证明:我们有:

    det ⁡ A ~ ( G ) = ∑ π ( − 1 ) sgn ⁡ ( π ) ∏ i = 1 n A ~ ( G ) i , π i \det \tilde{A}(G)=\sum_{\pi}(-1)^{\operatorname{sgn}(\pi)}\prod_{i=1}^n\tilde{A}(G)_{i,\pi_i} detA~(G)=π(1)sgn(π)i=1nA~(G)i,πi

    右侧的非零项形如 ( − 1 ) sgn ⁡ ( π ) ∏ i = 1 n ± x i , π i (-1)^{\operatorname{sgn}(\pi)}\prod\limits_{i=1}^n \pm x_{i,\pi_i} (1)sgn(π)i=1n±xi,πi,其中 ∀ i , ( i , π i ) ∈ E \forall i,(i,\pi_i)\in E i,(i,πi)E,这些边构成了 G G G 的一个环覆盖(边有向)。

    注意到,如果这个环覆盖 π \pi π 是一个奇环覆盖,那么我们考虑将其中编号最小的奇环上的边都反向,这对应了另一个环覆盖 π ′ \pi' π(且这个环覆盖也对应它),根据引理4或引理5可知此时 sgn ⁡ ( π ’ ) \operatorname{sgn}(\pi’) sgn(π) 没有变化,但改变了这个环中所有变量 x i , π i x_{i,\pi_i} xi,πi 的符号,所以此时 π ′ \pi' π 的贡献与 π \pi π 的贡献相反。

    那么所有 π \pi π 是奇环覆盖的贡献都会被抵消掉,所以若图 G G G 没有偶环覆盖,则必有 det ⁡ A ~ ( G ) = 0 \det \tilde{A}(G)= 0 detA~(G)=0

    接下来考虑偶环覆盖的贡献。由于每条边 ( u , v ) (u,v) (u,v) 所代表的变量 x u , v x_{u,v} xu,v 都是相互独立的,所以两种环覆盖 π , π ′ \pi,\pi' π,π 的贡献有可能抵消掉的必要条件是两种环覆盖所选出来的边集都是一样的(这里的边集是指无向边的集合)。

    那么我们考虑每一类偶环覆盖,同一类内边集相同,这些边组成一个还未给边定向的偶环覆盖。根据上面的证明过程同理可知若我们对环覆盖 π \pi π 中的某个偶环上的边全部反向得到环覆盖 π ′ \pi' π π ′ \pi' π 的贡献和 π \pi π 的贡献是相同的。那么无论我们怎么给这个偶环覆盖上的环定向,所得到的贡献总是相同的,那么它们之间就不可能相互抵消。

    于是,若图 G G G 存在偶环覆盖,则必有 det ⁡ A ~ ( G ) ≠ 0 \det \tilde{A}(G)\neq 0 detA~(G)=0,是个非零多项式。

随机化

Tutte 定理给了我们一个非常好的判定图 G G G 是否存在完美匹配的算法:我们只需要判断 det ⁡ A ~ ( G ) \det \tilde{A}(G) detA~(G) 是否等于 0 0 0 即可。但这个做法有一个显著的问题:由于 Tutte 矩阵的每一项都是一个变量,而变量的总数高达 O ( ∣ E ∣ ) O(|E|) O(E) 个,所以直接求其行列式时需要动态维护多项式,那就不能直接高斯消元,时间复杂度甚至是指数级的。

然而,我们只需要判断 det ⁡ A ~ ( G ) \det \tilde{A}(G) detA~(G) 是否为恒为 0 0 0。这是一个很经典的问题:对于一个 n n n 元多项式 F ( x 1 , ⋯   , x n ) F(x_1,\cdots,x_n) F(x1,,xn),我们要判断它是否恒为 0 0 0。我们可以使用随机化。

  • 引理 6(Schwartz-Zippel 引理):对于域 F \mathbb{F} F 上的一个不恒为 0 0 0 n n n d d d 度多项式 F ( x 1 , ⋯   , x n ) F(x_1,\cdots,x_n) F(x1,,xn),设 r 1 , ⋯   , r n r_1,\cdots,r_n r1,,rn n n n F \mathbb{F} F 中的独立选取的随机数,则:

    P r [ F ( r 1 , ⋯   , r n ) = 0 ] ≤ d ∣ F ∣ Pr[F(r_1,\cdots,r_n)=0]\leq \frac{d}{|\mathbb{F}|} Pr[F(r1,,rn)=0]Fd

    其中多项式的度数为其每个单项式度数的最大值(如单项式 2 x 2 y 2x^2y 2x2y 度数为 3 3 3)。

    证明:使用归纳证明。

    • 对于单变量( n = 1 n=1 n=1)的情况,此时 F F F 至多有 d d d 个根,我们恰好选到其中一个的概率不超过 d ∣ F ∣ \frac{d}{|\mathbb F|} Fd

    • 对于 n > 1 n>1 n>1 的情况,假设定理对于 n − 1 n-1 n1 的情况都成立,我们考虑把 F F F 看成以 x 1 x_1 x1 为主元的多项式:

      F ( x 1 , ⋯   , x n ) = ∑ i = 0 d x 1 i F i ( x 2 , ⋯   , x n ) F(x_1,\cdots,x_n)=\sum_{i=0}^dx_1^iF_i(x_2,\cdots,x_n) F(x1,,xn)=i=0dx1iFi(x2,,xn)

      根据定义,我们有 deg ⁡ F i ≤ d − i \deg F_i\leq d-i degFidi

      由于 F F F 不恒为 0 0 0,所以必定存在至少一个 i i i 使得 F i F_i Fi 不恒为 0 0 0,找到最大的这样的 i i i

      根据归纳假设, P r [ F i ( r 2 , ⋯   , r n ) = 0 ] ≤ d − i ∣ F ∣ Pr[F_i(r_2,\cdots,r_n)=0]\leq \frac{d-i}{|\mathbb{F}|} Pr[Fi(r2,,rn)=0]Fdi

      F i ( r 2 , ⋯   , r n ) ≠ 0 F_i(r_2,\cdots,r_n)\neq 0 Fi(r2,,rn)=0,则 F ( x 1 , ⋯   , x n ) F(x_1,\cdots,x_n) F(x1,,xn) 为关于 x 1 x_1 x1 的一元 i i i 次多项式,有:

      P r [ F ( r 1 , ⋯   , r n ) = 0 ∣ F i ( r 2 , ⋯   , r n ) ≠ 0 ] ≤ i ∣ F ∣ Pr[F(r_1,\cdots,r_n)=0|F_i(r_2,\cdots,r_n)\neq 0]\leq \frac{i}{|\mathbb{F}|} Pr[F(r1,,rn)=0∣Fi(r2,,rn)=0]Fi

      F ( r 1 , ⋯   , r n ) = 0 F(r_1,\cdots,r_n)=0 F(r1,,rn)=0 为事件 A A A F i ( r 2 , ⋯   , r n ) F_i(r_2,\cdots,r_n) Fi(r2,,rn) 为事件 B B B,那么:

      P r [ A ] = P r [ A ∩ B ] + P r [ A ∩ B ˉ ] = P r [ A ∣ B ] P r [ B ] + P r [ A ∣ B ˉ ] P r [ B ˉ ] ≤ P r [ B ] + P r [ A ∣ B ˉ ] ≤ d − i ∣ F ∣ + i ∣ F ∣ = d ∣ F ∣ \begin{aligned} Pr[A]&=Pr[A\cap B]+Pr[A\cap\bar B]\\ &=Pr[A|B]Pr[B]+Pr[A|\bar B]Pr[\bar B]\\ &\leq Pr[B]+Pr[A|\bar B]\\ &\leq \frac{d-i}{|\mathbb F|}+\frac{i}{|\mathbb F|}=\frac{d}{|\mathbb F|} \end{aligned} Pr[A]=Pr[AB]+Pr[ABˉ]=Pr[AB]Pr[B]+Pr[ABˉ]Pr[Bˉ]Pr[B]+Pr[ABˉ]Fdi+Fi=Fd

我们知道多项式, det ⁡ A ~ ( G ) \det \tilde A(G) detA~(G) 的度是 O ( n ) O(n) O(n) 级别的。因此我们不妨选取一个大质数 p p p,将运算在模 p p p 意义下进行,并通过为每个变量赋随机值的方式来判定图 G G G 是否存在完美匹配。由引理6可知,该算法错误的概率不超过 n p \frac{n}{p} pn,可以接受(实在不行搞多一次)。

至此,我们得到了一个 O ( n 3 ) O(n^3) O(n3) 的算法判定图 G G G 是否存在完美匹配。

构造完美匹配方案

咕。

求解最大匹配

咕。

Pfaffian 值(选读)

这一节仅为知识拓展,和一般图最大匹配的线性代数算法无关。

k = n 2 k=\frac{n}{2} k=2n,定义 A ~ ( G ) \tilde A(G) A~(G) 的 Pfaffian 值为:

Pf ( A ~ ( G ) ) = 1 k ! 2 k ∑ π ( − 1 ) sgn ⁡ ( π ) ∏ i = 1 k A ~ ( G ) π 2 i − 1 , π 2 i \text{Pf}(\tilde A(G))=\frac{1}{k!2^{k}}\sum_{\pi}(-1)^{\operatorname{sgn}(\pi)}\prod_{i=1}^k\tilde A(G)_{\pi_{2i-1},\pi_{2i}} Pf(A~(G))=k!2k1π(1)sgn(π)i=1kA~(G)π2i1,π2i

从组合意义角度考虑,可以看作是枚举了完美匹配并统计了它们的贡献。

  • 引理 7:对于长为 2 k 2k 2k 的排列 π \pi π,任意交换 ( π 2 i − 1 , π 2 i ) (\pi_{2i-1},\pi_{2i}) (π2i1,π2i) ( π 2 j − 1 , π 2 j ) (\pi_{2j-1},\pi_{2j}) (π2j1,π2j) sgn ⁡ ( π ) \operatorname{sgn}(\pi) sgn(π) 不会变化。

    证明:注意到任意交换连续四项的前两项和后两项, sgn ⁡ ( π ) \operatorname{sgn}(\pi) sgn(π) 不会变化。

我们可以发现对于任意一种完美匹配(无向),它对于 Pf ( A ~ ( G ) ) \text{Pf}(\tilde A(G)) Pf(A~(G)) 的贡献都相同:首先交换边与边之间的顺序不会影响贡献,而将一条边反向后, sgn ⁡ ( π ) \operatorname{sgn}(\pi) sgn(π) ∏ \prod 中的符号同时改变,故总体的符号也不变。

更具体地,一种完美匹配被统计的次数恰为 k ! 2 k k!2^k k!2k 次,前者是边与边之间的顺序,后者是每条边的定向。

于是 Pf ( A ~ ( G ) ) \text{Pf}(\tilde A(G)) Pf(A~(G)) 可以看做是枚举每一种完美匹配,并恰好统计一次其贡献。

事实上,Tutte 矩阵 A ~ ( G ) \tilde A(G) A~(G) 与其 Pfaffian 值有着更加紧密的联系,我们直接给出结论:

  • 定理 3(Cayley 定理)

    Pf ( A ~ ( G ) ) 2 = det ⁡ A ~ ( G ) \text{Pf}(\tilde A(G))^2=\det \tilde A(G) Pf(A~(G))2=detA~(G)

    证明:考虑 Pf ( A ~ ( G ) ) 2 \text{Pf}(\tilde A(G))^2 Pf(A~(G))2 的组合意义:它相当于枚举了两组完美匹配并统计二者贡献的乘积。

    而两组完美匹配恰好对应一组偶环覆盖(每个点度数为 2 2 2),那么我们不妨提取出某一组偶环覆盖 S S S(假设其中有 m m m 个偶环),并且查看它在等式左右的贡献。

    根据刚刚的推导和 Tutte 定理的证明过程,我们可以知道 S S S 在等式左边或右边每次被统计时的贡献都是相同的,所以我们只需证明 S S S 在等式左右的单次贡献相同,以及在等式左右被统计的次数相同即可。

    先看统计次数:

    • 等式右边:每个偶环有正向和反向之分,所以 S S S 在等式右边被统计的次数为 2 m 2^m 2m

    • 等式左边:我们知道,一个偶环 R R R 被分解为两组完美匹配 A , B A,B A,B 的方案是唯一的。那么对于 R R R 而言,他在 Pf ( A ~ ( G ) ) ⋅ Pf ( A ~ ( G ) ) \text{Pf}(\tilde A(G))\cdot \text{Pf}(\tilde A(G)) Pf(A~(G))Pf(A~(G)) 中就只有 “前者 A A A 后者 B B B” 或 “前者 B B B 后者 A A A” 两种可能。于是 S S S 在等式左边被统计的次数即为 2 m 2^{m} 2m

    再看单次贡献:那么不妨设这组偶环覆盖为 S = { { p 1 , 1 , ⋯   , p 1 , s 1 } , ⋯   , { p m , 1 , ⋯   , p m , s m } } S=\{\{p_{1,1},\cdots,p_{1,s_1}\},\cdots,\{p_{m,1},\cdots,p_{m,s_m}\}\} S={{p1,1,,p1,s1},,{pm,1,,pm,sm}},且第一组完美匹配为 B 1 = { ( p 1 , 1 , p 1 , 2 ) , ⋯   , ( p 1 , s 1 − 1 , p 1 , s 1 ) , ⋯   } B_1=\{(p_{1,1},p_{1,2}),\cdots,(p_{1,s_1-1},p_{1,s_1}),\cdots\} B1={(p1,1,p1,2),,(p1,s11,p1,s1),},那么第二组完美匹配应为 B 2 = { ( p 1 , 2 , p 1 , 3 ) , ⋯   , ( p 1 , s 1 , p 1 , 1 ) , ⋯   } B_2=\{(p_{1,2},p_{1,3}),\cdots,(p_{1,s_1},p_{1,1}),\cdots\} B2={(p1,2,p1,3),,(p1,s1,p1,1),}

    • 等式右边:结合引理5可推知,单次贡献为 ( − 1 ) n − m ∏ i = 1 m ∏ j = 1 s i A ~ ( G ) p i , j , p i , j + 1 (-1)^{n-m}\prod_{i=1}^{m}\prod_{j=1}^{s_i}\tilde A(G)_{p_{i,j},p_{i,j+1}} (1)nmi=1mj=1siA~(G)pi,j,pi,j+1

    • 等式左边:可以直接设 B 1 B_1 B1 对应的排列为 π 1 = { p 1 , 1 , p 1 , 2 , ⋯   , p 1 , s 1 − 1 , p 1 , s 1 , p 2 , 1 , p 2 , 2 , ⋯   , p 2 , s 2 − 1 , p 2 , s 2 , ⋯   } \pi_1=\{p_{1,1},p_{1,2},\cdots,p_{1,s_1-1},p_{1,s_1},\quad p_{2,1},p_{2,2},\cdots,p_{2,s_2-1},p_{2,s_2},\quad \cdots\} π1={p1,1,p1,2,,p1,s11,p1,s1,p2,1,p2,2,,p2,s21,p2,s2,} B 2 B_2 B2 对应的排列为 π 2 = { p 1 , 2 , p 1 , 3 , ⋯   , p 1 , s 1 , p 1 , 1 , p 2 , 2 , p 2 , 3 , ⋯   , p 2 , s 2 , p 2 , 1 , ⋯   } \pi_2=\{p_{1,2},p_{1,3},\cdots,p_{1,s_1},p_{1,1},\quad p_{2,2},p_{2,3},\cdots,p_{2,s_2},p_{2,1},\quad \cdots\} π2={p1,2,p1,3,,p1,s1,p1,1,p2,2,p2,3,,p2,s2,p2,1,}。那么单次贡献显然为:

      ( − 1 ) sgn ⁡ ( π 1 ) ( − 1 ) sgn ⁡ ( π 2 ) ∏ i = 1 m ∏ j = 1 s i A ~ ( G ) p i , j , p i , j + 1 (-1)^{\operatorname{sgn}(\pi_1)}(-1)^{\operatorname{sgn}(\pi_2)}\prod_{i=1}^m\prod_{j=1}^{s_i}\tilde A(G)_{p_{i,j},p_{i,j+1}} (1)sgn(π1)(1)sgn(π2)i=1mj=1siA~(G)pi,j,pi,j+1

      比较 sgn ⁡ ( π 1 ) \operatorname{sgn}(\pi_1) sgn(π1) sgn ⁡ ( π 2 ) \operatorname{sgn}(\pi_2) sgn(π2) 的异同:发现我们只需将 π 2 \pi_2 π2 中每个环 i i i p i , 1 p_{i,1} pi,1 移动至该环最前面(一共会造成奇数次交换),就能使 π 1 \pi_1 π1 π 2 \pi_2 π2 相同而抵消。所以 ( − 1 ) sgn ⁡ ( π 1 ) ( − 1 ) sgn ⁡ ( π 2 ) = ( − 1 ) m (-1)^{\operatorname{sgn}(\pi_1)}(-1)^{\operatorname{sgn}(\pi_2)}=(-1)^{m} (1)sgn(π1)(1)sgn(π2)=(1)m

    n n n 一定为偶数的情况下, ( − 1 ) n − m (-1)^{n-m} (1)nm ( − 1 ) m (-1)^m (1)m 相等,故二者单次贡献相同。

Cayley 定理有许多应用。例如,当我想统计每种完美匹配中的边的贡献和而非每种偶环覆盖中所有边的贡献和时,就可以先求出 Tutte 矩阵的行列式再开方得到其 Pfaffian 值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值