题解:P4897 【模板】最小割树(Gomory-Hu Tree)
Eznibuil
·
2023-01-30 00:06:52
·
题解
Upd on 2023.3.1:些许补充和修复。
Upd on 2023.5.2:为了方便检测 Gomory-Hu 算法的正确性,造了一道板子。
Upd on 2024.5.6:修正定理 1 的证明错误。
Upd on 2024.9.13:题解区 KaTeX 升级,进行小修复。
前言:很难相信 Gomory-Hu Tree 的板子题没人写 Gomory-Hu 算法的题解。不过话说回来这题也没必要用 Gomory-Hu Tree。
写个更易懂(也许?)的 Gomory-Hu 算法的粗略证明放这里。不太清晰(毕竟本人也不怎么懂),若需要好一点的请参见参考文献[2]。
最小割树(Gomory-Hu Tree),满足原图上的两点间的最小割大小和方案正好有一种是树上对应两点间的最小割大小和方案。
下面定义:图 G=(V,E) ,V 为点集,E 为边集,\lambda(x,y) 表示 x,y 之间的最小割。
Lemma 1
任取三点 a,b,c ,有 \lambda(a,b)\ge\min(\lambda(a,c),\lambda(c,b)) 。
证明:考虑 a,b,c 之间的割:设切断 a\leftrightarrow b 和 a\leftrightarrow c 的最小的割权值是 \alpha ,切断 a\leftrightarrow b 和 b\leftrightarrow c 的最小的割权值是 \beta ,切断 a\leftrightarrow c 和 b\leftrightarrow c 的最小的割权值是 \gamma ,则原式化为 \min(\alpha,\beta)\ge\min(\min(\alpha,\gamma),\min(\beta,\gamma)) ,得证。
由此可得 \lambda(a,b),\lambda(a,c),\lambda(b,c) 三者中必有两者相等,且另一个大于等于相等的两者。
然后立即得出以下推论:
Corollary 1
对于任意互不相同的 k 个点 s_1,s_2,\cdots,s_k\in V ,有 \lambda(s_1,s_k)\ge\min(\lambda(s_1,s_2),\lambda(s_2,s_3),\cdots,\lambda(s_{k-1},s_k)) 。
Theorem 1
若一棵树的每条边满足边权为最小割且割断此边分出的点集与最小割割出的点集相同,那么取任意两点 u,v ,令 s,t 为 u,v 的路径上最小的边,则 \lambda(u,v)=\lambda(s,t) 。
证明:根据引理 1,\lambda(u,v)\ge\lambda(s,t) ;另一方面,根据此树的定义,必然 s,t 的最小割可以成为 u,v 的割,有 \lambda(u,v)\le\lambda(s,t) 。综上得证。
注:此树即为 Gomory-Hu Tree。
定义一个割 W (一边的点的点集)的权函数 \delta(W) 为所有满足一端点在 W 而另一端点不在 W 的边的边权和。
Lemma 2.1
>证明:显然割两侧点集的权相等。
### Lemma 2.2
$\delta(W)$ 满足 submodular,亦即,$\delta(A)+\delta(B)\ge\delta(A\cap B)+\delta(A\cup B)$。
>证明:画出 Venn 图易证。
### Lemma 2.3
$\delta(W)$ 满足 posi-modular,亦即,$\delta(A)+\delta(B)\ge\delta(A-B)+\delta(B-A)$。
>证明:根据引理 2.1 和引理 2.2:
>$$\begin{aligned}\delta(A)+\delta(B)&=\delta(V-A)+\delta(B)\\&\ge\delta((V-A)\cap B)+\delta((V-A)\cup B)\\&=\delta(B-A)+\delta(V-(A-B))\\&=\delta(B-A)+\delta(A-B)\end{aligned}$$
### Key Lemma
令 $W$ 为 $s,t$ 之间的一个最小割一侧的点集,则对于任意点对 $u,v\in W,u\ne v$,存在 $u,v$ 间的一个最小割一侧的点集 $X$ 使得 $X\subseteq W$。
>证明:假设 $s\in W,s\in X,u\in X$,且 $X$ 不是 $W$ 的子集。
>
>1. $t\notin X$。根据引理 2.2 的 submodular 性质,$\delta(X)+\delta(W)\ge\delta(X\cap W)+\delta(X\cup W)$。另一方面,$X\cap W$ 是一个 $u,v$ 间的割,所以 $\delta(X\cap W)\ge\delta(X)$;同理 $\delta(X\cup W)\ge\delta(W)$。由此可得以上均取等号,则 $X\cap W$ 同样是 $u,v$ 最小割且满足其为 $W$ 的子集。
>1. $t\in X$。根据引理 2.3 的 posi-modular 性质,$\delta(X)+\delta(W)\ge\delta(X-W)+\delta(W-X)$,同上可得 $W-X$ 是 $u,v$ 间的最小割且满足其为 $W$ 的子集。
由 Key Lemma 可以发现最小割可以互不交叉,这是 Gomory-Hu Tree 存在的基础。
### Gomory-Hu 算法
以下是参考文献[1]给出的实现:(传入的 $G$ 是图,$R$ 是点集,返回的 $T$ 是树,$C_r$ 是划分)
$$\begin{aligned}&\underline{\text{{\bf function} GomoryHuAlg(G,R)\qquad\qquad\qquad\qquad\qquad\qquad\qquad\qquad\qquad\qquad}}\\&\textbf{if }|R|=1\textbf{ then}\\&\quad\textbf{return }T=(R,\varnothing),C_r=V\\&\textbf{else}\\&\quad\text{令 }r_1,r_2\in R,r_1\ne r_2,\text{且令 }W\text{ 为 }r_1,r_2\text{ 最小割一侧的点集},r_1\in W\\\\&\quad\langle\text{创建两个子问题}\rangle\\&\quad G_1=G\text{ 把 }V-W\text{ 缩成一个点 }v_1;R_1=R\cap W\\&\quad G_2=G\text{ 把 }W\text{ 缩成一个点 }v_2;R_2=R-W\\\\&\quad\langle\text{现在递归}\rangle\\&\quad T_1,(C^1_r|r\in R_1)=\operatorname{GomoryHuAlg}(G_1,R_1)\\&\quad T_2,(C^2_r|r\in R_2)=\operatorname{GomoryHuAlg}(G_2,R_2)\\\\&\quad\langle\text{注意 }r',r''\text{ {\bf不一定}是 }r_1,r_2\text{!}\rangle\\&\quad\text{令 }r'\text{ 为满足 }v_1\in C^1_{r'}\text{ 的点}\\&\quad\text{令 }r''\text{ 为满足 }v_2\in C^2_{r''}\text{ 的点}\\\\&\quad\langle\text{现在合并,并把 }v_1,v_2\text{ 从划分中去除}\rangle\\&\quad T=(R_1\cup R_2,E_{T_1}\cup E_{T_2}\cup\{r'-r''\})\qquad\text{注:此处的 }R_1\cup R_2\text{ 就是 }R\\&\quad\textbf{for }r\in R_1,r\ne r',C_r=C^1_r\\&\quad\textbf{for }r\in R_2,r\ne r'',C_r=C^2_r\\&\quad C_{r'}=C_{r'}-\{v_1\}\\&\quad C_{r''}=C_{r''}-\{v_2\}\\&\quad\textbf{return }T,C_r\\&\textbf{end if}\end{aligned}$$
### Theorem 2
上述算法对于一个输入的无向非负权图 $G=(V,E)$,一定可以返回一个原图的 Gomory-Hu Tree。
>证明:使用归纳法,首先当 $|V|=1$ 时树中只有唯一的一点,因此符合要求,接下来考虑 $|V|>1$ 的情况。
>
>首先划分然后递归,递归后返回了两个子图的 Gomory-Hu Tree,根据 Key Lemma,其他的不用管,因为不会影响,于是证明加上去的那条边合法即可,也就是 $\lambda(r_1,r_2)=\lambda(r',r'')$。
>
>可以发现 $r_1,r_2$ 之间的最小割一定是 $r',r''$ 之间的割,所以我们有 $\lambda(r_1,r_2)\ge\lambda(r',r'')$。分析缩了点的图,可以得到 $\lambda(r_1,r')\ge\lambda(r_1,r_2)$ 以及 $\lambda(r'',r_2)\ge\lambda(r_1,r_2)$。
>
>所以根据引理 1,$\lambda(r',r'')\ge\min(\lambda(r',r_1),\lambda(r_1,r_2),\lambda(r_2,r''))=\lambda(r_1,r_2)$,而前面已经得到 $\lambda(r_1,r_2)\ge\lambda(r',r'')$,综上证毕。
所以很快可以得出以下推论:
#### Corollary 2
对于一个输入的无向非负权图 $G=(V,E)$,Gomory-Hu 算法会正好执行 $|V|-1$ 次求最小割。
---
回到这道题,具体实现 Gomory-Hu 算法时,可以用一个全局的图和一个映射来模拟缩点,传入一个记录点集的 `std::vector<int>`,用全局 `bool[][]` 记录划分,用全局的 `std::vector<std::pair<std::pair<int,int>,int>>` 记录树边集。
可以发现其他题解的 Gusfield 算法和 Gomory-Hu 算法非常相似,除了连边时直接在求最小割的两个点之间连而且无需缩点。同样可以证明 Gusfield 算法给出了一个等价流树(equivalent flow tree),满足任意两点的最大流即为树上两点的最大流。
注 1:参考文献[2]给出了两种算法的非递归形式,可以去看一下。大意就是反正是拆分点集,可以用枚举每一个没有拆分完全的点集进行拆分。代码难度略低于递归形式。
注 2:显然 Gusfield 比 Gomory-Hu 好写而且这道题不要求方案。~~但这不是 Gomory-Hu 板子没人用 Gomory-Hu 写的理由。~~
下面是 Gomory-Hu 算法的基于 SAP 的实现,语言为 C++14,时间复杂度 $O(n^2+n\tau)$,其中 $\tau$ 是求一次最小割(最大流)的时间复杂度。
使用了很多 `std::vector::emplace_back()` 看似效率比较低,实际上因为缩点的缘故速度和 Gusfield 差不多快,甚至更快。
```cpp
#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
#include<vector>
const int N=2001,M=100001;
bool vis[N],C[N][N];
int f,s,t,len,las[N],nex[M],en[M],vol[M],he,ta,q[N],dis[N],cnt[N],maxfl,sh[N],fa[N],ans[N][N];
#define addedg(e,d,g) (nex[len]=las[e],las[e]=len,en[len]=d,vol[len++]=g)
#define addfl(e,d,g) (addedg(e,d,g),addedg(d,e,g))
std::vector<int>rs[N];
std::vector<std::pair<std::pair<int,int>,int>>T;
bool bfs()
{
int x;
for(int i=he=ta=0;i<f;i++)
vis[sh[i]]=0,cnt[sh[i]]=dis[sh[i]]=0;
vis[q[ta++]=sh[t]]=1,dis[sh[t]]=0;
while(he<ta)
{
cnt[dis[x=q[he++]]]++;
for(int _:rs[x])
for(int i=las[_];~i;i=nex[i])
if(!vis[sh[en[i]]])
vis[sh[en[i]]]=1,dis[sh[en[i]]]=dis[x]+1,q[ta++]=sh[en[i]];
}
assert(sh[s]!=sh[t]);
return vis[sh[s]];
}
int dfs(int x,int fl)
{
if(x==sh[t])
return maxfl+=fl,fl;
int d=0;
for(int _:rs[x])
for(int i=las[_],j;~i;i=nex[i])
if(vol[i]&&dis[sh[en[i]]]==dis[x]-1)
{
d+=j=dfs(sh[en[i]],vol[i]<fl-d?vol[i]:fl-d),vol[i]-=j,vol[i^1]+=j;
if(d==fl)
return d;
}
cnt[dis[x]]--;
if(!cnt[dis[x]])
dis[sh[s]]=f;
return cnt[++dis[x]]++,d;
}
void tag(int x)
{
vis[x]=1;
for(int _:rs[x])
for(int i=las[_];~i;i=nex[i])
if(vol[i^1]&&!vis[sh[en[i]]])
tag(sh[en[i]]);
return;
}
int mf(int S,int T)
{
s=S,t=T;
for(int i=maxfl=0;i<len;i+=2)
vol[i]=vol[i^1]=vol[i]+vol[i^1]>>1;
if(bfs())
while(dis[sh[s]]<f)
dfs(sh[s],1000000000);
for(int i=0;i<f;i++)
vis[sh[i]]=0;
return tag(sh[t]),maxfl;
}
void GomoryHu(const std::vector<int>&R)
{
if(R.size()==1)
{
for(int i=0;i<f;i++)
C[R[0]][sh[i]]=1;
return;
}
std::vector<int>R1,R2;
bool v[N];
int r1=R[0],r2=R[1],d=mf(r1,r2),tmp[N],rp,rpp;
for(int i=0;i<f;i++)
tmp[i]=-1,v[i]=vis[i];
for(int i:R)
if(vis[sh[i]])
R2.emplace_back(i);
else
R1.emplace_back(i);
for(int i=0,k;i<f;i++)
if(v[k=sh[i]]&&k!=r2)
for(int j=rs[k].size()-1;~j;j--)
tmp[rs[k][j]]=sh[rs[k][j]],sh[rs[k][j]]=r2;
for(int i=0;i<f;i++)
rs[i].clear();
for(int i=0;i<f;i++)
rs[sh[i]].emplace_back(i);
GomoryHu(R1);
for(int i=0;i<f;i++)
if(~tmp[i])
sh[i]=tmp[i],tmp[i]=-1;
for(int i=0;i<f;i++)
rs[i].clear();
for(int i=0;i<f;i++)
rs[sh[i]].emplace_back(i);
for(int i=0,k;i<f;i++)
if(!v[k=sh[i]]&&k!=r1)
for(int j=rs[k].size()-1;~j;j--)
tmp[rs[k][j]]=sh[rs[k][j]],sh[rs[k][j]]=r1;
for(int i=0;i<f;i++)
rs[i].clear();
for(int i=0;i<f;i++)
rs[sh[i]].emplace_back(i);
GomoryHu(R2);
for(int i=0;i<f;i++)
if(~tmp[i])
sh[i]=tmp[i];
for(int i=0;i<f;i++)
rs[i].clear();
for(int i=0;i<f;i++)
rs[sh[i]].emplace_back(i);
for(int i:R1)
if(C[i][r2])
{
rp=i,C[i][r2]=0;
break;
}
for(int i:R2)
if(C[i][r1])
{
rpp=i,C[i][r1]=0;
break;
}
T.emplace_back(std::make_pair(rp,rpp),d);
return;
}
void getans(int x,int y)
{
for(int i=las[y];~i;i=nex[i])
if(en[i]!=fa[y])
fa[en[i]]=y,ans[x][en[i]]=(ans[x][y]<vol[i]?ans[x][y]:vol[i]),getans(x,en[i]);
return;
}
int main()
{
int n,m,q,u,v,w;
std::vector<int>R;
scanf("%d%d",&n,&m),f=n+1;
for(int i=0;i<f;i++)
las[i]=-1,R.emplace_back(i),sh[i]=i,rs[i].emplace_back(i);
while(m--)
scanf("%d%d%d",&u,&v,&w),addfl(u,v,w);
GomoryHu(R);
for(int i=len=0;i<f;i++)
las[i]=-1;
for(auto j:T)
addfl(j.first.first,j.first.second,j.second);
for(int i=0;i<f;i++)
fa[i]=-1,ans[i][i]=1e9,getans(i,i);
scanf("%d",&q);
while(q--)
scanf("%d%d",&u,&v),printf("%d\n",ans[u][v]);
return 0;
}
```
### 参考文献
1. <https://courses.engr.illinois.edu/cs598csc/sp2010/Lectures/Lecture6.pdf>
1. 《浅谈无向图最小割问题的一些算法及应用》(绍兴一中王文涛,[2016 年国集论文](https://github.com/OI-wiki/libs/blob/master/%E9%9B%86%E8%AE%AD%E9%98%9F%E5%8E%86%E5%B9%B4%E8%AE%BA%E6%96%87/%E5%9B%BD%E5%AE%B6%E9%9B%86%E8%AE%AD%E9%98%9F2016%E8%AE%BA%E6%96%87%E9%9B%86.pdf))