网络流
- 设源点为 sss,汇点为 ttt,每条边 eee 的流量上限为 c(e)c(e)c(e),流量为 f(e)f(e)f(e)。
- 割 指对于某一顶点集合 P⊂VP \subset VP⊂V,从 PPP 出发指向 PPP 外部的那些原图中的边的集合,记作割 (P,V/ P)(P, V /\ P)(P,V/ P)。这些边的容量被称为割的容量。若 s∈P,t∈V/ Ps\in P, t\in V /\ Ps∈P,t∈V/ P,则称此时的割为 s−ts-ts−t 割。
- 对于任意的 s−ts-ts−t 流 FFF 和任意的 s−ts-ts−t 割 (P,V/ P)(P,V/\ P)(P,V/ P) 割,由每个点的流量平衡条件得:
F的流量=P出边总流量−P入边总流量≤割的容量 F 的流量 = P出边总流量 - P 入边总流量 \le 割的容量 F的流量=P出边总流量−P入边总流量≤割的容量 - 对于在残量网络中不断增广得到的流 FFF,设其对应的残量网络中从 sss 出发可到达的顶点集为 SSS,则对于 SSS 指向 V/ SV/\ SV/ S 的边 eee 有 f(e)=c(e)f(e) = c(e)f(e)=c(e),而对 V/ SV/\ SV/ S 指向 SSS 的边有 f(e)=0f(e) = 0f(e)=0,则:
F的流量=S出边总流量−S入边总流量=S出边总流量=割的容量 F 的流量 = S 出边总流量 - S入边总流量 = S出边总流量 = 割的容量 F的流量=S出边总流量−S入边总流量=S出边总流量=割的容量 - 因而 FFF 为最大流,同时 (S,V/ S)(S,V/\ S)(S,V/ S) 为最小割,即最大流等于最小割。
Dinic 算法
- 主要思想即每次寻找最短的增广路,构造分层图,并沿着它多路增广。
- 每次多路增广完成后最短增广路长度至少增加 1,构造分层图次数为 O(n)\mathcal O(n)O(n),在同一分层图中,每条增广路都会被至少一条边限制流量(我们称之为瓶颈),显然任意两条增广路的瓶颈均不相同,因而增广路总数为 O(m)\mathcal O(m)O(m),加上当前弧优化,我们就能避免对无用边多次检查,寻找单条增广路的时间复杂度为 O(n)\mathcal O(n)O(n),总时间复杂度 O(n2m)\mathcal O(n^2m)O(n2m)。
- 这只是一个粗略的上界,实际的复杂度分析要结合具体的图模型,例如若最大流上限较小(设为 FFF),时间复杂度 O(F(n+m))\mathcal O(F(n+m))O(F(n+m))。
template <class T>
inline T Min(T x, T y) {return x < y ? x : y;}
const int N = 1e4 + 5;
const int M = 2e5 + 5;
const ll Maxn = 1e15;
int nxt[M], to[M], adj[N], que[N], cur[N], lev[N]; ll cap[M];
int n, m, src, des, qr, T = 1;
inline void linkArc(int x, int y, ll w)
{
nxt[++T] = adj[x]; adj[x] = T; to[T] = y; cap[T] = w;
nxt[++T] = adj[y]; adj[y] = T; to[T] = x; cap[T] = 0;
}
inline bool Bfs()
{
for (int x = 1; x <= n; ++x)
cur[x] = adj[x], lev[x] = -1;
// 初始化具体的范围视建图而定,这里点的范围为 [1,n]
que[qr = 1] = src;
lev[src] = 0;
for (int i = 1; i <= qr; ++i)
{
int x = que[i], y;
for (int e = adj[x]; e; e = nxt[e])
if (cap[e] > 0 && lev[y = to[e]] == -1)
{
lev[y] = lev[x] + 1;
que[++qr] = y;
if (y == des)
return true;
}
}
return false;
}
inline ll Dinic(int x, ll flow)
{
if (x == des)
return flow;
int y, delta; ll res = 0;
for (int &e = cur[x]; e; e = nxt[e])
if (cap[e] > 0 && lev[y = to[e]] > lev[x])
{
delta = Dinic(y, Min(flow - res, (ll)cap[e]));
if (delta)
{
cap[e] -= delta;
cap[e ^ 1] += delta;
res += delta;
if (res == flow)
break ;
//此时 break 保证下次 cur[x] 仍有机会增广
}
}
if (res != flow)
lev[x] = -1;
return res;
}
inline ll maxFlow()
{ //求完最大流后与 src 属于同一点集的点满足 lev[x] != -1
ll res = 0;
while (Bfs())
res += Dinic(src, Maxn);
return res;
}
- 单位网络 在该网络中,所有边的流量均为 1,除源汇点以外的所有点,都满足入边或者出边最多只有一条。
- 结论 对于包含二分图最大匹配在内的单位网络,Dinic\text{Dinic}Dinic 算法求解最大流的时间复杂度为 O(mn)\mathcal O(m\sqrt n)O(mn)。
证明 对于单位网络,每条边最多被考虑一次,一轮增广的时间复杂度为 O(m)\mathcal O(m)O(m)。
假设我们已经完成了前 n\sqrt nn 轮增广,还需找到 ddd 条增广路才能找到最大流,每条增广路的长度至少为 n\sqrt nn。这些增广路不会在源点和汇点以外的点相交,因而至少经过了 dnd\sqrt ndn 个点,d≤nd \le \sqrt nd≤n,则至多还需增广 n\sqrt nn 轮,总时间复杂度 O(mn)\mathcal O(m\sqrt n)O(mn)。
经典模型
- 二者选其一的最小割 有 nnn 个物品和两个集合 A,BA,BA,B,若第 iii 个物品没有放入 AAA 集合花费 aia_iai,没有放入 BBB 集合花费 bib_ibi,还有若干个限制条件,若 uiu_iui 和 viv_ivi 不在一个集合则花费 wiw_iwi。
- 源点 sss 向第 iii 个点连一条容量为 aia_iai 的边,第 iii 个点向汇点 ttt 连一条容量为 bib_ibi 的边,在 uiu_iui 和 viv_ivi 之间连容量为 wiw_iwi 的双向边,最小割即最小花费。
- 最大权闭合子图 给定一张有向图,每个点都有一个权值(可为负),选择一个权值和最大的子图,使得子图中每个点的后继都在子图中。
- 若点权为正,则 sss 向该点连一条容量为点权的边。
- 若点权为负,则该点向 ttt 连一条容量为点权的相反数的边。
- 原图上所有边的容量设为 +∞+\infty+∞。
- 则答案为正点权之和减去最小割。
- 分糖果问题 nnn 个糖果 mmm 个小孩,小孩 iii 对糖果 jjj 有偏爱度 ai,j=1/2a_{i,j} = 1/2ai,j=1/2,设 ci,j=0/1c_{i,j} = 0/1ci,j=0/1 表示小孩 iii 是否分得了糖果 jjj,小孩 iii 觉得高兴当且仅当 ∑j=1nci,jai,j≥bi\sum\limits_{j = 1}^{n} c_{i,j} a_{i,j}\ge b_ij=1∑nci,jai,j≥bi,判断是否存在方案使所有小孩都高兴。
- 偏爱度 ai,j=1/2a_{i,j} = 1/2ai,j=1/2 不好建图,转换思路先分配所有 ai,j=2a_{i,j} = 2ai,j=2 的糖果。
- sss 向所有糖果连一条容量为 1 的边,小孩 iii 向 ttt 连一条容量为 ⌊bi2⌋\lfloor \frac{b_i}{2} \rfloor⌊2bi⌋ 的边。
- 对于所有满足 ai,j=2a_{i,j} = 2ai,j=2 的边,令糖果 jjj 向小孩 iii 连一条容量为 1 的边。
- 求得最大流 ansansans,则存在方案当且仅当 ans+n≥∑j=1mbians + n\ge \sum \limits_{j = 1}^{m}b_ians+n≥j=1∑mbi。
- 动态流问题 宽为 www 的河上有 nnn 块石头,第 iii 块坐标 (xi,yi)(x_i,y_i)(xi,yi),同一时刻最大承受人数为 cic_ici,现有 mmm 个游客想要渡河,每人每次最远跳 ddd 米 ,单次耗时 1 秒,求全部渡河的最少时间( n,m≤50n,m \le 50n,m≤50)。
- 答案取值范围较小可暴力枚举,将每一时刻的石头都视作一点,在石头上跳跃可视作从第 ttt 时刻的石头 iii 跳向第 t+1t + 1t+1 时刻的石头 jjj,每次将时刻加一,建出新的点和边后跑最大流,直至总流量大于等于 mmm。
费用流
- 在最大流的前提下使该网络总花费最小。
SSP 算法
-
每次寻找单位费用最小的增广路进行增广,直至图中不存在增广路为止。
-
设流量为 iii 的时候最小费用为 fif_ifi,假设初始网络上没有负圈,f0=0f_0 = 0f0=0。
-
假设用 SSP\text{SSP}SSP 算法求出的 fif_ifi 是最小费用,我们在 fif_ifi 的基础上,找到一条最短的增广路,从而求出 fi+1f_{i + 1}fi+1,此时 fi+1−fif_{i + 1} - f_ifi+1−fi 就是这条最短增广路的长度。
-
假设存在更小的 fi+1f_{i + 1}fi+1,设其为 fi+1′f_{i+1}'fi+1′,则 fi+1′−fif'_{i + 1} - f_ifi+1′−fi 一定对应一个经过至少一个负圈的增广路。若残量网络中存在至少一个负圈,则可在不增加 sss 流出的流量的情况下使费用减小,与 fif_ifi 是最小费用矛盾。
-
综上,SSP\text{SSP}SSP 算法可以正确求出无负圈网络的最小费用最大流,设最大流为 FFF,总时间复杂度 O(Fnm)\mathcal O(Fnm)O(Fnm)。
-
因为随着流量的增大,最短路一定不降,因而费用关于流量的函数是一个凸函数。
template <class T>
inline T Min(T x, T y) {return x < y ? x : y;}
const int N = 5e3 + 5;
const int M = 1e5 + 5;
const int Maxn = 1e9;
int nxt[M], to[M], cap[M], que[M], cst[M], adj[N], dis[N];
bool vis[N]; int n, m, src, des, ans, T = 1, qr;
inline void linkArc(int x, int y, int w, int z)
{
nxt[++T] = adj[x]; adj[x] = T; to[T] = y; cap[T] = w; cst[T] = z;
nxt[++T] = adj[y]; adj[y] = T; to[T] = x; cap[T] = 0; cst[T] = -z;
}
inline bool SPFA()
{
for (int x = 1; x <= n; ++x)
dis[x] = Maxn, vis[x] = false;
// 初始化具体的范围视建图而定,这里点的范围为 [1,n]
dis[que[qr = 1] = src] = 0;
for (int i = 1, x, y; i <= qr; ++i)
{
vis[x = que[i]] = false;
for (int e = adj[x]; e; e = nxt[e])
if (cap[e] > 0 && dis[y = to[e]] > dis[x] + cst[e])
{
dis[y] = dis[x] + cst[e];
if (!vis[y])
vis[que[++qr] = y] = true;
}
}
return dis[des] < Maxn;
}
inline int Dinic(int x, int flow)
{
if (x == des)
{
ans += flow * dis[des];
return flow;
}
vis[x] = true;
int y, delta, res = 0;
for (int e = adj[x]; e; e = nxt[e])
if (!vis[y = to[e]] && cap[e] > 0 && dis[y] == dis[x] + cst[e])
// vis 数组防止 dfs 在总费用为 0 的环上死循环
{
delta = Dinic(y, Min(flow - res, cap[e]));
if (delta)
{
cap[e] -= delta;
cap[e ^ 1] += delta;
res += delta;
if (res == flow)
break ;
}
}
return res;
}
inline int MCMF()
{
ans = 0;
int res = 0;
while (SPFA())
res += Dinic(src, Maxn);
return res;
}
经典模型
- 餐巾计划问题 一家餐厅在接下来的 TTT 天内需用餐巾,第 iii 天需要 rir_iri 块干净餐巾,餐厅可任意购买单价为 ppp 的干净餐巾,或者将脏餐巾送往快洗部(单块餐巾需洗 mmm 天,花费 fff)或慢洗部(单块餐巾需洗 nnn 天,花费 sss),求这 TTT 天内的最小花费。
- 主要的难点在于需将干净餐巾和脏餐巾区分开,第 iii 天分设点 i,i′i,i'i,i′,流向这两点的流量分别表示第 iii 天的干净餐巾和脏餐巾。
- sss 向 iii 连一条容量为 +∞+\infty+∞ 、费用为 ppp 的边,iii 向 ttt 连一条容量为 rir_iri、费用为 0 的边。
- sss 向 i′i'i′ 连一条容量为 rir_iri、费用为 0 的边,i′i'i′ 向 i+mi + mi+m 连一条容量为 +∞+\infty+∞,费用为 fff 的边,向 i+ni + ni+n 连一条容量为 +∞+\infty+∞,费用为 sss 的边,向 (i+1)′(i + 1)'(i+1)′ 连一条容量为 +∞+\infty+∞,费用为 000 的边。
- 显然该建图能使到 ttt 的边满流,则最小费用即为所求。
- 费用流转二分图最大匹配 若除去费用的建图为二分图且费用不为 0 的边均在二分图一侧,可将有费用的边从小到大排序,按照这一顺序跑匈牙利算法,容易证明最大匹配即对应最小费用,可将时间复杂度降至 O(nm)\mathcal O(nm)O(nm)。
上下界网络流
- 以下 (x,y,l,r)(x, y, l, r)(x,y,l,r) 代指 xxx 向 yyy 容量上限为 lll 下限为 rrr 的边,(x,y,w)(x,y,w)(x,y,w) 代指 xxx 向 yyy,连容量为 www 的边。
无源汇上下界可行流
-
对原图中每条边 (x,y,l,r)(x,y,l,r)(x,y,l,r),假设每条边已有初始流量 lll,在新图中连边 (x,y,r−l)(x, y, r - l)(x,y,r−l)。
-
对于图中每个点 xxx,记点 xxx 的初始流入流量减去初始流出流量为 Δx\Delta_xΔx,新建超级源汇点 S′S'S′ 和 T′T'T′:
- 若 Δx>0\Delta_x > 0Δx>0,连边 (S′,x,Δx)(S', x, \Delta_x)(S′,x,Δx)。
- 若 Δx<0\Delta_x < 0Δx<0,连边 (x,T′,−Δx)(x, T', -\Delta_x)(x,T′,−Δx)。
-
对新图跑最大流算法,若 S′S'S′ 的出边全部满流则存在可行流,可行流中每条边的流量等于初始流量加上当前流量。
cin >> n >> m;
src = n + 1, des = n + 2;
for (int i = 1, x, y, l, r; i <= m; ++i)
{
cin >> x >> y >> l >> r;
linkArc(x, y, r - l);
ide[i] = T; lim[i] = l;
delt[x] -= l, delt[y] += l;
}
ll tot = 0;
for (int i = 1; i <= n; ++i)
if (delt[i] > 0)
linkArc(src, i, delt[i]), tot += delt[i];
else if (delt[i] < 0)
linkArc(i, des, -delt[i]);
if (maxFlow() == tot) //注意结点初始化的范围为n+2
{
cout << "YES\n";
for (int i = 1; i <= m; ++i)
cout << cap[ide[i]] + lim[i] << '\n';
}
else cout << "NO\n";
有源汇上下界可行流
- 在无源汇上下界可行流的基础上,在原有的源汇点 SSS 和 TTT 间增加连边 (T,S,∞)(T, S, \infty)(T,S,∞),跑最大流后该边反向弧的流量即为可行流的流量。
cin >> n >> m >> src >> des;
for (int i = 1, x, y, l, r; i <= m; ++i)
{
cin >> x >> y >> l >> r;
linkArc(x, y, r - l);
delt[x] -= l, delt[y] += l;
}
linkArc(des, src, Maxn);
int key = T;
src = n + 1, des = n + 2;
ll tot = 0;
for (int i = 1; i <= n; ++i)
if (delt[i] > 0)
linkArc(src, i, delt[i]), tot += delt[i];
else if (delt[i] < 0)
linkArc(i, des, -delt[i]);
if (maxFlow() == tot) //注意结点初始化的范围为 n+2
cout << cap[key] << '\n';
else
cout << "please go home to sleep\n";
有源汇上下界最大流
- 在有源汇上下界可行流的基础上,直接跑原图 SSS 到 TTT 的最大流,此时 (T,S,∞)(T,S,\infty)(T,S,∞) 的反向弧流量会被退回,因此所求即为有源汇上下界最大流。
cin >> n >> m >> src >> des;
for (int i = 1, x, y, l, r; i <= m; ++i)
{
cin >> x >> y >> l >> r;
linkArc(x, y, r - l);
delt[x] -= l, delt[y] += l;
}
linkArc(des, src, Maxn);
int _src = src, _des = des;
src = n + 1, des = n + 2;
ll tot = 0;
for (int i = 1; i <= n; ++i)
if (delt[i] > 0)
linkArc(src, i, delt[i]), tot += delt[i];
else if (delt[i] < 0)
linkArc(i, des, -delt[i]);
if (maxFlow() == tot) //注意结点初始化的范围为 n+2
{
src = _src, des = _des;
cout << maxFlow() << '\n';
}
else
cout << "please go home to sleep\n";
有源汇上下界最小流
- 在有源汇上下界可行流的基础上,删去 (T,S,∞)(T,S,\infty)(T,S,∞),之后跑 TTT 到 SSS 的最大流,此时可行流减去最大流的流量即为有源汇上下界最小流。
cin >> n >> m >> src >> des;
for (int i = 1, x, y, l, r; i <= m; ++i)
{
cin >> x >> y >> l >> r;
linkArc(x, y, r - l);
delt[x] -= l, delt[y] += l;
}
int _src = src, _des = des;
src = n + 1, des = n + 2;
ll tot = 0;
for (int i = 1; i <= n; ++i)
if (delt[i] > 0)
linkArc(src, i, delt[i]), tot += delt[i];
else if (delt[i] < 0)
linkArc(i, des, -delt[i]);
linkArc(_des, _src, Maxn);
if (maxFlow() == tot)
{
ll allow = cap[T];
src = _des, des = _src;
cap[T] = cap[T - 1] = 0;
cout << allow - maxFlow() << '\n';
}
else
cout << "please go home to sleep\n";
最小费用可行流
- 在可行流的边中增加费用即可,注意要加上初始流量的费用。
最小割树
- 即 Gomory-Hu Tree,用于求无向图中任意两点间的最小割。
- 设当前的点集为 VVV,从 VVV 取出两点 sss 和 ttt,求 原图 中 sss 到 ttt 的最小割 www,在新图中连边 (s,t,w)(s,t,w)(s,t,w),最小割将 VVV 分为两个点集 V1,V2V_1,V_2V1,V2,递归下去直至每个点集仅包含一点,新图构成了一棵树,若采用的是 Dinic 算法,时间复杂度 O(n3m)\mathcal O(n^3m)O(n3m)。
- 结论 原图中任意两点间的最小割等于树上两点间的路径最小值。
证明 先考虑证明三个点的情况,即存在两条边 (a,b,w1)(a,b,w_1)(a,b,w1) 和 (b,c,w2)(b,c,w_2)(b,c,w2),需证明 aaa 和 ccc 间的最小割 w=min{w1,w2}w = \min\{w_1,w_2\}w=min{w1,w2}。
www 将原图分为两个点集,仅可能存在两种情况之一,故得证。
- a,ba,ba,b 属于同一点集,最小割为 w2w_2w2。
- b,cb,cb,c 属于同一点集 ,最小割为 w1w_1w1。
对于一条路径,不断选择一端的三点,用 (a,c,min{w1,w2})(a,c,\min\{w_1,w_2\})(a,c,min{w1,w2}) 替换 (a,b,w1)(a,b,w_1)(a,b,w1) 和 (b,c,w2)(b,c,w_2)(b,c,w2) 即可。
inline void solve(int l, int r)
{ // curp[i] 初始为 i
if (l == r)
return ;
src = curp[l], des = curp[l + 1];
for (int i = 2; i <= T; i += 2)
{
int w = cap[i] + cap[i ^ 1] >> 1;
cap[i] = cap[i ^ 1] = w;
}
int w = maxFlow();
e[src].emplace_back(des, w);
e[des].emplace_back(src, w);
vector<int> _cur;
for (int i = l; i <= r; ++i)
{
int x = curp[i];
if (lev[x] != -1)
_cur.emplace_back(x);
}
int mid = l + _cur.size() - 1;
for (int i = l; i <= r; ++i)
{
int x = curp[i];
if (lev[x] == -1)
_cur.emplace_back(x);
}
for (int i = l; i <= r; ++i)
curp[i] = _cur[i - l];
solve(l, mid);
solve(mid + 1, r);
}
无向图最小割
- Stoer-Wagner 算法可在 O(n3)\mathcal O(n^3)O(n3) 时间内求解无向图最小割。
- 具体流程为,每次 O(n2)\mathcal O(n^2)O(n2) 找到一对点 s,ts,ts,t 的最小割(见函数
calcMinCut
和以下描述),之后将 s,ts,ts,t 及其之间的边缩为一点,直至图中仅剩一点时,过程中求得的最小割即为答案。 - 定义 d(x,y)d(x,y)d(x,y) 为邻接矩阵,权值函数 w(A,x)=∑y∈Ad(x,y)w(A,x) = \sum_{y\in A}d(x,y)w(A,x)=∑y∈Ad(x,y),每次将 w(A,x)w(A,x)w(A,x) 最大且不属于 AAA 的 xxx 加入 AAA,直至 A=VA = VA=V。
- 结论 设 AxA_xAx 表示所有在 xxx 之前加入的点的点集,s,ts,ts,t 为最后加入 AAA 的两个结点,则 w(At,t)w(A_t,t)w(At,t) 为 s,ts,ts,t 的最小割。
证明 即证明对于任意 s−ts-ts−t 割 CCC,总有 w(At,t)≤w(C)w(A_t,t) \le w(C)w(At,t)≤w(C),其中 w(C)=∑(x,y)∈Cd(x,y)w(C) = \sum_{(x,y)\in C}d(x,y)w(C)=∑(x,y)∈Cd(x,y)。
定义一个点 xxx 被 激活 当且仅当 AxA_xAx 中最后一个点与 xxx 分属 CCC 的两侧,且 CxC_xCx 为 CCC 在 Ax∪{x}A_x \cup \{x\}Ax∪{x} 下的诱导割,现在归纳证明,对于一个被激活的点 xxx,恒有:
w(Ax,x)≤w(Cx) w(A_x,x) \le w(C_x) w(Ax,x)≤w(Cx)
- 对于第一个被激活的点,显然有 w(Ax,x)=w(Cx)w(A_x,x) = w(C_x)w(Ax,x)=w(Cx)。
- 否则,设当前被激活的点为 xxx,上一个被激活的点为 yyy,则
w(Ax,x)=w(Ay,x)+w(Ax−Ay,x)≤w(Ay,y)+w(Ax−Ay,x)≤w(Cy)+w(Ax−Ay,x)≤w(Cx) \begin{aligned} w(A_x,x) &= w(A_y, x) + w(A_x - A_y, x) \\ &\le w(A_y, y) + w(A_x - A_y, x)\\ &\le w(C_y) + w(A_x - A_y, x) \\ & \le w(C_x) \end{aligned} w(Ax,x)=w(Ay,x)+w(Ax−Ay,x)≤w(Ay,y)+w(Ax−Ay,x)≤w(Cy)+w(Ax−Ay,x)≤w(Cx)
由 w(At,t)w(A_t,t)w(At,t) 的定义,ttt 一定被激活,代入 ttt 即得到 w(At,t)≤w(Ct)=w(C)w(A_t, t) \le w(C_t) = w(C)w(At,t)≤w(Ct)=w(C),证毕。
- 该结论也意味着对于一个无向图,总存在一条边 (s,t)(s,t)(s,t) 使得 ttt 的所有邻边是 s,ts,ts,t 的最小割,并且可在类似 Dijkstra 的时间复杂度内找到。
#include <bits/stdc++.h>
template <class T>
inline void CkMin(T &x, T y) {x > y ? x = y : 0;}
using std::ios;
using std::cin;
using std::cout;
const int N = 60;
const int Maxn = 1e9;
int n, m, src, des;
int dis[N][N], w[N], ord[N];
bool del[N], vis[N];
inline int calcMinCut(int x)
{
for (int i = 1; i <= n; ++i)
w[i] = 0, vis[i] = false;
for (int i = 1; i <= n - x + 1; ++i)
{
int id = 0;
for (int j = 1; j <= n; ++j)
if (!del[j] && !vis[j] && (!id || w[j] > w[id]))
id = j;
vis[id] = true, ord[i] = id;
for (int j = 1; j <= n; ++j)
if (!del[j] && !vis[j])
w[j] += dis[id][j];
}
src = ord[n - x], des = ord[n - x + 1];
return w[des];
}
inline void Contraction()
{
del[des] = true;
for (int j = 1; j <= n; ++j)
{
dis[src][j] += dis[des][j];
dis[j][src] += dis[j][des];
}
}
inline int StoerWagner()
{
int res = Maxn;
for (int i = 1; i < n; ++i)
{
CkMin(res, calcMinCut(i));
Contraction();
}
return res;
}
int main()
{
srand(time(0));
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> m;
int x, y, z;
while (m--)
{
cin >> x >> y >> z;
dis[x][y] += z;
dis[y][x] += z;
}
cout << StoerWagner() << '\n';
return 0;
}
二分图最大匹配
- 记图 G=(V,E)G = (V,E)G=(V,E)。
- 匹配 GGG 中两两没有公共端点的边集合 M⊆EM \subseteq EM⊆E。
- 边覆盖 GGG 中的任意顶点都至少是 FFF 中某条边的边集合 F⊆EF \subseteq EF⊆E。
- 独立集 GGG 中两两互不相连的顶点集 S⊆VS \subseteq VS⊆V。
- 点覆盖 GGG 中任意边都有至少一个端点属于 PPP 的顶点集合 P⊆VP \subseteq VP⊆V。
- 结论1 对于不存在孤立点的图,∣最大匹配∣+∣最小边覆盖∣=∣V∣|最大匹配|+|最小边覆盖| = |V|∣最大匹配∣+∣最小边覆盖∣=∣V∣。
证明 设最大匹配数为 xxx,则其覆盖的点数为 2x2x2x,剩余点两两之间没有边。则最少需要增加 ∣V∣−2x|V| - 2x∣V∣−2x 条边才能将所有点覆盖,则最小边覆盖数为 x+∣V∣−2x=∣V∣−xx + |V| - 2x = |V| - xx+∣V∣−2x=∣V∣−x。
- 结论2 ∣最大独立集∣+∣最小点覆盖∣=∣V∣|最大独立集| + |最小点覆盖| = |V|∣最大独立集∣+∣最小点覆盖∣=∣V∣。
证明 只需证明独立集和点覆盖一一对应且互为关于 VVV 的补集即可。取一个点覆盖关于 VVV 的补集,若其不是一个独立集,则存在两点有公共边,不难发现这与点覆盖的定义矛盾。
- 结论3 在二分图中,∣最大匹配∣=∣最小点覆盖∣|最大匹配|=|最小点覆盖|∣最大匹配∣=∣最小点覆盖∣。
证明 设最大匹配数为 xxx,即让匹配中的每条边都和其一个端点关联。xxx 个点是足够的,否则若存在一条边未被覆盖,加入这条边能得到一个更大的匹配。xxx 个点是必需的,因为匹配的 xxx 条边两两无公共点。
-
结论4 在有向无环图中,∣最小点不相交路径覆盖∣+∣最大匹配∣=∣V∣|最小点不相交路径覆盖|+|最大匹配|=|V|∣最小点不相交路径覆盖∣+∣最大匹配∣=∣V∣。这里的最大匹配指的是,将原图每个点拆成入点和出点两点,对于有向边 x→yx \to yx→y,将 xxx 的入点向 yyy 的出点连边,在该二分图上求得的最大匹配。
- 若需求 ∣最小点可相交路径覆盖∣|最小点可相交路径覆盖|∣最小点可相交路径覆盖∣,可以每个点为起点 BFS 求出传递闭包,传递闭包上建图即可,传递闭包中图的边数可能达到 O(∣V∣2)\mathcal O(|V|^2)O(∣V∣2),时间复杂度 O(∣V∣∣E∣+∣V∣2.5)\mathcal O(|V||E|+|V|^{2.5})O(∣V∣∣E∣+∣V∣2.5),空间复杂度 O(∣V∣2)\mathcal O(|V|^2)O(∣V∣2),输出方案记录每个入点的匹配点即可,时间复杂度 (∣V∣)\mathcal (|V|)(∣V∣)。
- 若 ∣E∣|E|∣E∣ 较小,也可以不求传递闭包,将有向边的容量设为 +∞+\infty+∞,并将所有的出点向对应的入点连一条容量为 +∞+\infty+∞ 的边,即可实现传递闭包的效果,直接跑网络流即可,因为最大流上限为 ∣V∣|V|∣V∣,时间复杂度 O(∣V∣(∣V∣+∣E∣))\mathcal O(|V|(|V|+|E|))O(∣V∣(∣V∣+∣E∣)),空间复杂度 O(∣V∣+∣E∣)\mathcal O(|V|+|E|)O(∣V∣+∣E∣),实际上严格优于前述做法。该做法的输出方案较为麻烦,具体来说,我们根据所有匹配边建出一张图,若一个点的出边比入边多则它可以作为一条链的起点,暴力往下跳即可,实现时将本质相同的边压缩成一条可将空间复杂度降至 O(∣V∣+∣E∣)\mathcal O(|V|+|E|)O(∣V∣+∣E∣),时间复杂度 O(∣V∣2)\mathcal O(|V|^2)O(∣V∣2),参考代码如下:
// C 为二分图一侧的点数,src = 2 * C + 1 // 注意孤立点以及删去一条链可能会产生新的起点的情况 for (int x = 1; x <= C; ++x) for (int e = adj[x]; e; e = nxt[e]) if (to[e] > C && to[e] < src && to[e] - C != x && cap[e ^ 1] > 0) { re[x].emplace_back(std::make_pair(to[e] - C, cap[e ^ 1])); ++lre[x]; sre[x] += cap[e ^ 1]; ind[to[e] - C] += cap[e ^ 1]; } tis = 0; for (int t = 1; t <= C; ++t) { for (int x = 1; x <= C; ++x) while (sre[x] > ind[x]) { ++tis; int u = x; ans[u] = tis; while (sre[u]) { pir &v = re[u][lre[u] - 1]; int vid = v.first; if (--v.second == 0) re[u].pop_back(), --lre[u]; --sre[u]; u = vid; ans[u] = tis; --ind[u]; } } }
证明 只需证明二分图中匹配与原图中的路径覆盖一一对应且总和为 ∣V∣|V|∣V∣。对于每个没有匹配边的出点,我们都能构造一条路径,若其对应的入点存在匹配边,则将该匹配边加入该路径同时继续考虑该匹配边的出点直至其对应的入点不存在匹配边。得到的所有路径恰能覆盖所有点,且没有匹配边的出点和入点恰好能两两配对分别构成所有路径的起点和终点。
- Dilworth定理 对于任意有限偏序集,其最大反链中的元素个数必等于最小链划分中链的个数。
- 有限偏序集的最小链划分即其哈斯图对应的有向无环图的 ∣最小点可相交路径覆盖∣|最小点可相交路径覆盖|∣最小点可相交路径覆盖∣,用上述方法求解即可。
证明 设 mmm 为最大反链的元素个数,∣X∣=n|X| = n∣X∣=n,只要证明偏序集 XXX 可被划分成 mmm 个链即可。
考虑用归纳法证明,当结点数 n=1n = 1n=1 时显然成立 n=1n = 1n=1 时显然成立,下面证明 n>1n > 1n>1 时的情形,设该有限偏序集中所有极小点构成的集合为 LLL,所有极大点构成的集合为 GGG,分两种情况讨论:
若存在最大反链 AAA,使得 A≠LA \not = LA=L 且 A≠GA \not = GA=G。构造:
A+={x∣x∈X∧∃a∈A,a≤x}A−={x∣x∈X∧∃a∈A,x≤a} A^+=\{x|x\in X \wedge \exist a\in A,a\le x\} \\ A^-=\{x|x\in X \wedge \exist a\in A,x \le a\} \\ A+={x∣x∈X∧∃a∈A,a≤x}A−={x∣x∈X∧∃a∈A,x≤a}
则容易得到 ∣A+∣<∣X∣|A^+|<|X|∣A+∣<∣X∣、∣A−∣<∣X∣|A^-|<|X|∣A−∣<∣X∣ 且 A+∪A−=X,A+∩A−=AA^+\cup A^- =X,A^{+}\cap A^-=AA+∪A−=X,A+∩A−=A,由归纳假设可将 A+A^{+}A+ 和 A−A^{-}A− 的 mmm 链划分拼接起来即可得到 XXX 的 mmm 链划分。若只存在反链 A=LA = LA=L 或 A=GA = GA=G,取极小元 xxx 和极大元 yyy 满足 x≤yx\le yx≤y(x,yx,yx,y 可相等),则由归纳假设 X−{x,y}X -\{x,y\}X−{x,y} 最大反链的元素个数为 m−1m - 1m−1,存在 m−1m - 1m−1 链划分,增加链 x≤yx\le yx≤y 可得到 XXX 的 mmm 链划分。
- Dilworth定理的对偶形式 最长链中元素个数必等于最小反链划分中反链的个数。
证明 设最长链的长度为 mmm,最小反链划分的数目为 MMM。
- 由于在任意一个反链中选取超过一个元素都违反链的定义,容易得到 m≤Mm \le Mm≤M。
- 考虑每次删去当前偏序集中的所有极小点,恰好删除 mmm 次能够将所有点删除,设第 iii 次删点构成的极小点集合为 AiA_iAi,则 A1,A2,…,AmA_1,A_2,\dots,A_mA1,A2,…,Am 构成一个反链划分,故有 M≤mM \le mM≤m。
综上所述,m=Mm = Mm=M,原命题得证。
匈牙利算法
- 从每个点出发尝试找到一条增广路,时间复杂度 O(nm)\mathcal O(nm)O(nm)。
inline bool Hungary(int x)
{
int y;
for (arc *e = adj[x]; e; e = e->nxt)
if (!mateR[y = e->to])
return mateR[y] = x, true;
for (arc *e = adj[x]; e; e = e->nxt)
{
if (vis[y = e->to] == tis)
continue ;
vis[y] = tis;
if (Hungary(mateR[y]))
return mateR[y] = x, true;
}
return false;
}
inline int maxMatch()
{
int cnt = 0;
for (int i = 1; i <= n; ++i)
{
++tis;
if (Hungary(i))
++cnt;
}
return cnt;
}
二分图最大权匹配
Kuhn-Munkres算法
- 该算法用于 O(n3)\mathcal O(n^3)O(n3) 求二分图的最大权完美匹配,若二分图左右部的点数不相同,需将点数补至相同,并将所有不存在的边的权设为 0。
- 若题目对是否是完备匹配有要求,需将不存在的边设为 −∞-\infty−∞(具体值需保证一旦选这种边就比所有不选这种边的方案更劣),此时也能处理原图边权有负数的情况。
- 可行顶标 对于二分图 <X,E,Y><X,E,Y><X,E,Y>,对于 x∈Xx \in Xx∈X 分配权值 labx(x)\text{labx}(x)labx(x),对于 y∈Yy\in Yy∈Y 分配权值 laby(y)\text{laby}(y)laby(y),对于所有边 (x,y)∈E(x,y)\in E(x,y)∈E 满足 w(x,y)≤labx(x)+laby(y)w(x,y) \le \text{labx}(x) + \text{laby}(y)w(x,y)≤labx(x)+laby(y)。
- 相等子图 在一组可行顶标下原图的生成子图,包含所有点但只满足包含 labx(x)+laby(y)=w(x,y)\text{labx}(x) + \text{laby}(y) = w(x,y)labx(x)+laby(y)=w(x,y) 的边 (x,y)(x,y)(x,y)。
- 定理 对于某组可行顶标,若其相等子图内存在完美匹配,该匹配为原图的最大权完美匹配。
证明 设可行顶标下相等子图中的完美匹配为 M′M'M′,对于原二分图任意一组完美匹配 MMM,其边权和
val(M)=∑(x,y)∈Mw(x,y)≤∑x∈Xlabx(x)+∑y∈Ylaby(y)=val(M′) \text{val}(M) = \sum \limits_{(x,y)\in M}w(x,y) \le \sum\limits_{x\in X}\text{labx}(x) + \sum \limits_{y \in Y} \text{laby}(y) = \text{val}(M') val(M)=(x,y)∈M∑w(x,y)≤x∈X∑labx(x)+y∈Y∑laby(y)=val(M′)
故若 M′M'M′ 存在,M′M'M′ 即为最大权完美匹配。
- 下面的算法过程将证明一定能够通过调整顶标,使得相等子图内存在完美匹配。
- 初始可行顶标 labx(x)=maxy∈Y{w(x,y)},laby(y)=0\text{labx}(x) = \max\limits_{y\in Y}\{w(x,y)\}, \text{laby}(y) = 0labx(x)=y∈Ymax{w(x,y)},laby(y)=0,在相等子图内找一个未匹配的点,尝试寻找增广路,若能找到直接增广,否则将形成一棵交错树。
- 交错树是由从一个未匹配点出发沿着 非匹配边-匹配边 交替的所有路径构成的树,将树中顶点与 XXX 的交集记为 SSS,与 YYY 的交集记为 TTT,令 S′=X−S,T′=Y−TS' = X - S, T' = Y - TS′=X−S,T′=Y−T(S/S′,T/T′S/S',T/T'S/S′,T/T′ 程序中分别用 visx(x)\text{visx}(x)visx(x) 和 visy(y)\text{visy}(y)visy(y) 标示),如下图所示。
- 在相等子图中:
- S−T′S-T'S−T′ 的边不存在,若其为匹配边违反交错树的定义,若其为非匹配边则会形成增广路或者使交错树扩大。
- S′−TS'-TS′−T 的边一定是非匹配边,否则将使交错树扩大。
- 将 SSS 中的顶标 −a-a−a,将 TTT 中的顶标 +a+a+a,可以发现:
- S−TS-TS−T 和 S′−T′S'-T'S′−T′ 在相等子图中的边均无变化。
- S−T′S-T'S−T′ 的边对应顶标和减少,可能作为非匹配边加入相等子图,由上述性质,增加的边将形成增广路或使交错树扩大。
- S′−TS'-TS′−T 的边对应顶标和增加,不可能加入相等子图。
- 设 slacky(y)=minx∈S{labx(x)+laby(y)−w(x,y)}\text{slacky}(y) = \min\limits_{x\in S}\{\text{labx}(x) + \text{laby}(y) - w(x,y)\}slacky(y)=x∈Smin{labx(x)+laby(y)−w(x,y)}(当且仅当 y∈T′y \in T'y∈T′ 该值才有意义),则令 a=miny∈T′{slacky(y)}a = \min\limits_{y\in T'}\{\text{slacky}(y)\}a=y∈T′min{slacky(y)},每次修改顶标时,必定会有至少一条产生增广路或使交错树扩大的非匹配边加入相等子图。
- 在遍历交错树的过程中,记 pre(y)=x(x∈X,y∈Y)\text{pre}(y) = x(x\in X, y\in Y)pre(y)=x(x∈X,y∈Y) 表示交错树上 x,yx,yx,y 通过非匹配边相连,若产生了增广路,根据 pre\text{pre}pre 更新匹配边即可完成增广。
- 若使交错树扩大, 更新 slacky\text{slacky}slacky,在交错树增加的结点中重复上述过程直至完成增广,由于左右部点数相同,这一过程一定能够结束。
- 遍历交错树的部分通过 BFS\text{BFS}BFS 实现。总共需要完成 nnn 次增广,每次增广中 BFS\text{BFS}BFS 的总时间复杂度为 O(n2)\mathcal O(n^2)O(n2),更新顶标的次数为 O(n)\mathcal O(n)O(n)(每次更新都会使 TTT 扩大),每次更新后维护 slacky\text{slacky}slacky 并确定 aaa 的时间复杂度也为 O(n)\mathcal O(n)O(n),故总的时间复杂度为 O(n3)\mathcal O(n^3)O(n3)。
typedef long long ll;
const int N = 405;
const int Maxn = 2e9;
int que[N], w[N][N], slacky[N];
int labx[N], laby[N], matex[N], matey[N], pre[N];
bool visx[N], visy[N];
int nl, nr, qr, n, m; ll ans;
inline bool Augment(int y)
{
if (matey[y])
{
que[++qr] = matey[y];
visx[matey[y]] = visy[y] = true;
return false;
}
else
{
while (y)
{
int x = pre[y];
matey[y] = x;
std::swap(matex[x], y);
}
return true;
}
}
inline void bfsHungary(int src)
{
for (int i = 1; i <= n; ++i)
{
pre[i] = 0;
visx[i] = visy[i] = false;
slacky[i] = Maxn;
}
visx[que[qr = 1] = src] = true;
while (1)
{
for (int i = 1, x; i <= qr; ++i)
{
x = que[i];
for (int y = 1; y <= n; ++y)
if (!visy[y])
{
int delta = labx[x] + laby[y] - w[x][y];
if (delta > slacky[y])
continue ;
pre[y] = x;
if (delta > 0)
slacky[y] = delta;
else if (Augment(y))
return ;
}
}
int nxt, delta = Maxn;
for (int y = 1; y <= n; ++y)
if (!visy[y] && slacky[y] < delta)
delta = slacky[y], nxt = y;
for (int i = 1; i <= n; ++i)
{
if (visx[i])
labx[i] -= delta;
if (visy[i])
laby[i] += delta;
else
slacky[i] -= delta;
}
qr = 0;
if (Augment(nxt))
return ;
}
}
int main()
{
read(nl); read(nr); read(m);
n = Max(nl, nr);
int x, y, z;
while (m--)
{
read(x); read(y); read(z);
CkMax(w[x][y], z);
CkMax(labx[x], z);
}
for (int i = 1; i <= n; ++i)
bfsHungary(i);
for (int i = 1; i <= n; ++i)
ans += w[i][matex[i]];
put(ans), putchar('\n');
for (int i = 1; i <= nl; ++i)
put(w[i][matex[i]] ? matex[i] : 0), putchar(' ');
}
二分图稳定匹配
问题描述
- 对于任意的 x∈Xx \in Xx∈X,存在一个由 YYY 内所有结点构成的偏爱列表 prfX[x]prf_X[x]prfX[x],列表中结点排名越靠前对 xxx 适配度越高,记 rkX[x][y]rk_X[x][y]rkX[x][y] 表示 yyy 在 prfX[x]prf_X[x]prfX[x] 中的排名。
- 对于任意的 y∈Yy\in Yy∈Y,同样定义 prfY[y]prf_Y[y]prfY[y] 和 rkY[y][x]rk_Y[y][x]rkY[y][x]。
- 对于二分图 <X,Y,E><X,Y,E><X,Y,E>,某一匹配是稳定的(Stable Matching)当且仅当(记 xxx 的匹配结点为 matX[x]mat_X[x]matX[x],yyy 的匹配结点为 matY[y]mat_Y[y]matY[y]):
- 不存在 x,yx,yx,y 满足 x≠matY[y]x \not = mat_Y[y]x=matY[y],且:
- rkX[y]<rkX[matX[x]]rk_X[y] < rk_X[mat_X[x]]rkX[y]<rkX[matX[x]]
- rkY[x]<rkY[matY[y]]rk_Y[x] < rk_Y[mat_Y[y]]rkY[x]<rkY[matY[y]]
- 不存在 x,yx,yx,y 满足 x≠matY[y]x \not = mat_Y[y]x=matY[y],且:
Gale-Shapley 算法
- 描述该算法的 python 代码如下:
rkx = rky = matx = maty = dict()
for x in prfx.keys():
for i in range(len(prfx[x])):
rkx[(x, prfx[x][i])] = i
for y in prfy.keys():
for i in range(len(prfy[y])):
rky[(y, prfy[y][i])] = i
for x in prfx.keys():
prfx[x].reverse()
list = [x for x in prfx.keys()]
while len(list) > 0:
x = list.pop()
while len(prfx[x]) > 0:
y = prfx[x].pop()
if y not in maty:
maty[y] = x
matx[x] = y
break
elif rky[(y, x)] < rky[(y, maty[y])]:
del matx[maty[y]]
list.append(maty[y])
maty[y] = x
matx[x] = y
break
- 关于该算法的正确性:
- 如果 ∣X∣=∣Y∣|X| = |Y|∣X∣=∣Y∣,最后总能找到一个匹配,且 prfX[x]prf_X[x]prfX[x] 中的元素不会重复遍历,时间复杂度 O(∣X∣∣Y∣)\mathcal O(|X||Y|)O(∣X∣∣Y∣)。
- 考虑任何不在现有匹配中的 (x,y)(x,y)(x,y):
- 若 xxx 从未尝试与 yyy 匹配,则说明 rkX[x][matX[x]]<rkX[x][y]rk_X[x][mat_X[x]] < rk_X[x][y]rkX[x][matX[x]]<rkX[x][y]。
- 若 xxx 尝试过与 yyy 匹配,则说明 rkY[y][matY[y]]<rkY[y][x]rk_Y[y][mat_Y[y]] < rk_Y[y][x]rkY[y][matY[y]]<rkY[y][x]。
- 故该匹配是稳定的。
- 定义1 配对 (x,y)(x,y)(x,y) 是有效的当且仅当存在一个稳定匹配使之包含 (x,y)(x,y)(x,y)。
- 定义2 记 yxy_xyx 表示在列表 prfX[x]prf_X[x]prfX[x] 中最靠前的 yyy,一个匹配为 XXX-最优匹配当且仅当若 (x,yx)(x,y_x)(x,yx) 是有效的,则该匹配一定包含 (x,yx)(x,y_x)(x,yx)。
- 结论 Gale-Shapley 算法产生的匹配 MMM 一定为 XXX-最优匹配。
证明 记 y=yxy = y_xy=yx,假设 (x,y)(x,y)(x,y) 是有效的且 MMM 不包含 (x,y)(x,y)(x,y)。
设 MMM 中与 yyy 匹配的是 x′x'x′,由算法流程 rkY[y][x′]<rkY[y][x]rk_Y[y][x']<rk_Y[y][x]rkY[y][x′]<rkY[y][x](要么 xxx 匹配后被 x′x'x′ 替代,要么 x′x'x′ 先匹配后无法被 xxx 替代)。
由有效配对的定义,一定存在稳定匹配 M′M'M′ 包含 (x,y)(x,y)(x,y)。
记 M′M'M′ 中与 x′x'x′ 匹配的是 y′y'y′,能够得到匹配 MMM 的条件是 rkX[x′][y]<rkX[x′][y′]rk_X[x'][y] < rk_X[x'][y']rkX[x′][y]<rkX[x′][y′],则由于 (x′,y)(x',y)(x′,y) 的存在,M′M'M′ 是不稳定的,与假设矛盾。
图的着色
点着色
- 设图 GGG 的点色数为 χ(G)\chi(G)χ(G),则显然有 χ(G)≤Δ(G)+1\chi(G) \le \Delta(G)+1χ(G)≤Δ(G)+1。
- Brooks 定理 若 GGG 不为完全图或奇环,则 χ(G)≤Δ(G)\chi(G) \le \Delta(G)χ(G)≤Δ(G)。
证明 设 ∣V(G)∣=n|V(G)|=n∣V(G)∣=n,考虑数学归纳法。
首先,n≤3n\le 3n≤3 时,命题显然成立。
根据归纳法,假设对于 n−1n - 1n−1 的命题成立。
不妨只考虑 Δ(G)\Delta(G)Δ(G)-正则图,因为对于非正则图来说,可以看作在正则图里删去一些边构成的,而这一过程并不会影响结论。
对于任意不是完全图也不是奇圈的正则图 GGG,任取其中一点 vvv,考虑子图 H=G−vH = G - vH=G−v,由归纳假设知 χ(H)≤Δ(H)≤Δ(G)\chi(H)\le \Delta(H) \le \Delta(G)χ(H)≤Δ(H)≤Δ(G),接下来我们只需证明在 HHH 中插入 vvv 不会影响结论即可。
若 Δ(H)<Δ(G)\Delta(H) < \Delta(G)Δ(H)<Δ(G),无需再做证明,我们只考虑 Δ(H)=Δ(G)\Delta(H) = \Delta(G)Δ(H)=Δ(G) 的情况。
令 Δ=Δ(G)\Delta = \Delta(G)Δ=Δ(G),设 HHH 染的 CCC 种颜色分别为 c1,c2,…,cΔc_1, c_2, \dots, c_{\Delta}c1,c2,…,cΔ,vvv 的 Δ\DeltaΔ 个邻接点为 v1,v2,…,vΔv_1, v_2, \dots, v_{\Delta}v1,v2,…,vΔ。若 vvv 的邻接点个数不足 Δ\DeltaΔ 个或存在任意两点颜色相同,同样无需再做证明。
设所有在 HHH 中染成 cic_ici 或 cjc_jcj 的点以及它们之间的所有边构成子图 Hi,jH_{i,j}Hi,j。不妨假设任意 2 个不同的点 vi,vjv_i,v_jvi,vj 一定在 Hi,jH_{i,j}Hi,j 的同一个连通分量中,否则若在两个连通分量中的话,可以交换其中一个连通分量所有点的颜色,从而使 vi,vjv_i,v_jvi,vj 颜色相同,即能有多余的颜色对 vvv 进行染色,无需再做证明。
这里的交换颜色指的是若图中只有两种颜色 a,ba,ba,b,那么把图中原来染成颜色 aaa 的点全部染成颜色 bbb,把图中原来染成颜色 bbb 的点全部染成颜色 aaa。
我们设上述连通分量为 Ci,jC_{i,j}Ci,j,取出 Ci,jC_{i,j}Ci,j 中一条路径记作 Pi,jP_{i,j}Pi,j,则恒有 Ci,j=Pi,jC_{i,j} = P_{i,j}Ci,j=Pi,j。因为 viv_ivi 在 HHH 中的度为 Δ−1\Delta-1Δ−1,所以 viv_ivi 在 HHH 中的邻接点颜色一定两两不同,否则可以给 viv_ivi 染别的颜色,从而和 vvv 的其他邻接点颜色重复,所以 viv_ivi 在 Ci,jC_{i,j}Ci,j 中邻接点数量为 1。若 Ci,j≠Pi,jC_{i,j} \not = P_{i,j}Ci,j=Pi,j,设在 Ci,jC_{i,j}Ci,j 中从 viv_ivi 开始沿着 Pi,jP_{i,j}Pi,j 遇到的第一个度数大于 2 的点为 uuu,注意到 uuu 的邻接点最多只用了 Δ−2\Delta - 2Δ−2 种颜色,所以 uuu 可以重新染色,从而使 vi,vjv_i,v_jvi,vj 不连通。
沿用这一技术,我们可以证明对于 3 个不同的点 vi,vj,vkv_i,v_j,v_kvi,vj,vk,V(Ci,j)∩V(Cj,k)={vj}V(C_{i,j})\cap V(C_{j,k}) = \{v_j\}V(Ci,j)∩V(Cj,k)={vj}。假设存在 w∈V(Ci,j)∩V(Cj,k)w \in V(C_{i,j})\cap V(C_{j,k})w∈V(Ci,j)∩V(Cj,k),若 w≠vjw \not = v_jw=vj,则 www 必被染色为 cjc_jcj,且恰有两个被染色为 cic_ici 的邻接点和两个被染色为 cjc_jcj 的邻接点,注意到 www 的邻接点最多只用了 Δ−2\Delta - 2Δ−2 种颜色,所以 www 同样可以重新染色。
若 vvv 的邻接点两两相邻,则必有 Δ=n\Delta = nΔ=n,即 GGG 为完全图。否则不妨设 v1,v2v_1,v_2v1,v2 不相邻,在 C1,2C_{1,2}C1,2 取 v1v_1v1 的邻接点 www,交换 C1,3C_{1,3}C1,3 中的颜色,则 w∈V(C1,2)∩V(C2,3)w \in V(C_{1,2})\cap V(C_{2,3})w∈V(C1,2)∩V(C2,3),与上述结论矛盾。
至此命题证明完毕。
斯坦纳树
- 以以下题面为例,主要把握其思想:
- 给定连通图 GGG 中的 nnn 个点与 kkk 个关键点,求能包含 kkk 个关键点的生成树的最小边权,k≪nk \ll nk≪n。
- 设 fx,Sf_{x,S}fx,S 表示以 xxx 为根且包含关键点集合 SSS 的生成树的最小边权,在外层从小到大枚举 SSS,转移分为两类:
- fx,S=min{fx,S,fx,S−T+fx,T}f_{x,S} = \min\{f_{x,S}, f_{x,S-T}+f_{x,T}\}fx,S=min{fx,S,fx,S−T+fx,T}
- 松弛,可通过 Dijkstra 或 SPFA 实现,fx,S=min{fx,S,fy,S+w(x,y)}f_{x,S} = \min\{f_{x,S},f_{y,S} + w(x,y)\}fx,S=min{fx,S,fy,S+w(x,y)}
- 总时间复杂度 O(2k(n+m)log(n+m)+3kn)\mathcal O(2^k(n +m)\log(n + m)+3^kn)O(2k(n+m)log(n+m)+3kn)。
竞赛图
- 定义 将 nnn 个点的无向完全图任意定向即可得到竞赛图。
- 性质1 竞赛图缩点后呈链状,即求出竞赛图缩点后的拓扑序后,任意两个强连通分量之间的连边一定都是从拓扑序小的连向拓扑序大的。
证明 对强连通分量个数归纳,若新增的强连通分量拓扑序最大,则所有连向其的边同向。
- 性质2 竞赛图中每个强连通分量中存在哈密顿回路。
证明 对点数 n(n≥3)n(n\ge3)n(n≥3) 归纳,若新增的点连向原有 nnn 个点的所有边均同向,则这 n+1n + 1n+1 个点不构成强连通分量,否则总可以找到 nnn 个点中的两个点,使得它们在哈密顿回路上相邻且连向新增点的方向相反。
- 性质3 竞赛图中存在一条哈密顿路径。
证明 因为属于不同强连通分量间的点均有连边,由 性质2 取每个强连通分量中的哈密顿回路相连即可。
- 性质4 对于点数为 nnn 的强连通竞赛图,∀3≤l≤n\forall3\le l\le n∀3≤l≤n,其一定存在长度为 lll 的简单环。
证明 考虑对点数 nnn 归纳,由 性质1,删除第 nnn 个点后原图变为若干个强连通分量的链状图,由归纳条件,每个强连通分量均可以构造出不超过点数的简单环,且第 nnn 个点一定有指向拓扑序最小的强连通分量的边,拓扑序最大的强连通分量一定有指向 nnn 的边,不难得到构造长度不超过 nnn 的简单环的方案。
- 性质5 在竞赛图中若点 uuu 的出度大于等于点 vvv 的出度,则 uuu 一定可以到达 vvv。
证明 考虑证明其逆否命题,由 性质1,仅需证明 uuu 所在强连通分量拓扑序大于 vvv 所在强连通分量拓扑序的情况,此时显然 vvv 的出度大于 uuu 的出度。
- 兰道定理 将竞赛图的出度序列排序后得到 s1,s2,…,sns_1,s_2,\dots,s_ns1,s2,…,sn,该序列合法当且仅当 ∀2≤k≤n,∑i=1ksi≥(k2)\forall 2 \le k\le n,\sum\limits_{i = 1}^{k}s_i\ge\binom{k}{2}∀2≤k≤n,i=1∑ksi≥(2k) 且 ∑i=1nsi=(n2)\sum\limits_{i = 1}^{n}s_i = \binom{n}{2}i=1∑nsi=(2n)。
证明 其必要性显然,因为任取点数为 kkk 的导出子图都满足该条件。考虑证明其充分性,构造一个所有边均是大点连向小点的竞赛图,则其 si′=i−1s_i'=i-1si′=i−1,上述不等式可变形为 ∑i=1ksi≥∑i=1ksi′\sum\limits_{i = 1}^{k}s_i\ge \sum\limits_{i = 1}^{k}s_i'i=1∑ksi≥i=1∑ksi′,现在尝试通过不断调整该图,使得每个不等式均能取等号,每次操作如下:
- 找到第一个 xxx 使得 sx>sx′s_x > s_x'sx>sx′。
- 在 xxx 后找到第一个 zzz 使得 sz<sz′s_z < s_z'sz<sz′
- 因而有 sz′>sz≥sx>sx′s_z'>s_z\ge s_x > s_x'sz′>sz≥sx>sx′,即 sz′−sx′≥2s_z' - s_x'\ge 2sz′−sx′≥2,此时必存在点 yyy,使得边 z→yz \to yz→y 和 y→xy \to xy→x 存在,将这两条边反向,则 sz′s'_zsz′ 减少 1,sx′s_x'sx′ 增加 1,sy′s_y'sy′ 不变,原不等式仍成立。
强连通竞赛图计数
- 设 fnf_nfn 表示 nnn 个点的强连通竞赛图个数,考虑通过容斥原理计算 fnf_nfn,枚举拓扑序最靠前的强连通分量的大小,则剩余点连向该强连通分量的边的方向固定,而剩余点内部的连边是任意的,则对于 n≥1n \ge 1n≥1,有:
fn=2(n2)−∑i=1n−1(ni)fi2(n−i2)2(n2)n!=∑i=1nfii!2(n−i2)(n−i)! \begin{aligned} f_n = 2^{\binom{n}{2}}- \sum\limits_{i=1}^{n - 1}\binom{n}{i}f_{i} 2^{\binom{n - i}{2}} \\ \dfrac{2^{\binom{n}{2}}}{n!} = \sum \limits_{i = 1}^{n}\dfrac{f_i}{i!} \dfrac{2^{\binom{n - i}{2}}}{(n - i)!} \end{aligned} fn=2(2n)−i=1∑n−1(in)fi2(2n−i)n!2(2n)=i=1∑ni!fi(n−i)!2(2n−i) - 设 F(x)=∑n≥1fnn!xnF(x) = \sum\limits_{n\ge 1}\dfrac{f_n}{n!} x^nF(x)=n≥1∑n!fnxn,G(x)=∑n≥02(n2)n!xnG(x) = \sum \limits_{n \ge 0}\dfrac{2^{\binom{n}{2}}}{n!}x^nG(x)=n≥0∑n!2(2n)xn,则上式可表达为:
G=FG+1F=1−1G \begin{aligned} G = FG + 1 \\ F = 1 - \dfrac{1}{G} \\ \end{aligned} G=FG+1F=1−G1 - 通过多项式求逆计算出 FFF 即可。