[Asta's Legacy]两个和图论有关的小东西
前言
朋友一生一起走
那些日子不再有 —— 周华健 朋友
更小常数的 RMQ LCA
对于一棵有根树,从根进行深度优先搜索,每次搜索到一个节点和回溯到一个节点时都将这个节点加入序列末尾,得到的序列称为欧拉序。
此时两点之间 LCA 即为两点欧拉序上首次出现位置之间的欧拉序最小节点。
然后套一个 st 表就可以实现 \(\mathcal{O}(n \log n) - \mathcal{O}(1)\) 的 LCA 了。
但是可以发现这个序列长度带个二倍常数,这可不能忍。
然后发现只加一遍结果也是对的,证明参见 $e 神仙的博客:https://www.cnblogs.com/skip1978/p/12240164.html
如果再挂一个状压 RMQ 就是 \(\mathcal{O}(n) - \mathcal{O}(1)\) 了。
常数以及代码量都暴打(?)其他几种在线 LCA,最大的问题就是空间是 \(n \log n\) 的。
最后有一个简单的引理,就是许多个点的 LCA 就是其中 dfn 最大和最小这两个点的 LCA,之前见到有个题是从这个出发的来思考的。
放个代码:
\(n \log n\):
int dfc;
int dfn[N];
int st[19][N];
void dfs(int u,int _f) {
st[0][dfc] = _f,dfn[u] = ++dfc;
repg(i,u) {
const int v = e[i].to;
if(v == _f)
continue;
dfs(v,u);
}
}
inline int cmp(int x,int y) {
return dfn[x] < dfn[y] ? x : y;
}
inline void InitLCA(int r) {
dfs(r,0);
for(int j = 1;(1 << j) <= dfc;++j)
for(int i = 1;i + (1 << j) - 1 < dfc;++i)
st[j][i] = cmp(st[j - 1][i],st[j - 1][i + (1 << (j - 1))]);
}
#define l2(x) (31 - __builtin_clz(x))
inline void LCA(int u,int v) {
if(u == v) return writeI(u),void();
u = dfn[u],v = dfn[v];
if(u > v) std::swap(u,v);
const int k = l2(v - u);
writeI(cmp(st[k][u],st[k][v - (1 << k)]));
}
#undef l2
状压 RMQ(太长了,关键部分差不多,只挂链接)
https://www.luogu.com.cn/record/73698324
最小割树
一个无向图,求任意两点间最大流/最小割的一个方法。
但是因为我们不关心最小割的具体方案,只是求个权值,我们只建立 等价流树。
等价流树是一棵无根带权树,\(n\) 个点对应原图上的 \(n\) 个点,树上两点间最小边权即原图两点之间最小割权值。
下面介绍等价流树的 Gusfield 算法。
(最开始我认为这就是 Gomory-Hu Tree,看了 2016年集训队论文 才发现不一样)
首先我们有 \(n\) 个点,取出两个在 原图 求最小割,树上连接这两个点,边权为这一次最小割。
然后对于剩下的点,由这次最小割分为两组,与源点相连的和与汇点相连的,我们分为两个集合 \(S,T\) 分别向下递归即可。
这次最小割是 \(S\) 中任取一点和 \(T\) 中任取一点的一个合法的割,但是不一定最小,所以是树上路径最小值。
然后因为有 \(n - 1\) 条树边可以发现做了 \(n - 1\) 次最大流,复杂度就是 \(\mathcal{O} (n \times MaxFlow)\)。
代码:
int ans[N][N];
int pr[N];
int n;
void build(int l,int r) {
if(l == r) return ;
int ps = pr[l],pt = pr[l + 1];
int w = Dinic(ps,pt,n);
ans[ps][pt] = ans[pt][ps] = w;
int p1 = 0,p2 = 0;
static int tmp1[N],tmp2[N];
rep(i,l,r) if(dep[pr[i]])
tmp1[++p1] = pr[i];
else
tmp2[++p2] = pr[i];
rep(i,1,p1) pr[i + l - 1] = tmp1[i];
rep(i,1,p2) pr[i + l + p1 - 1] = tmp2[i];
build(l,l + p1 - 1);
build(l + p1,r);
rep(i,1,p1) rep(j,1,p2) {
const int u = pr[i + l - 1];
const int v = pr[j + l + p1 - 1];
ans[u][v] = ans[v][u] = std::min({ans[u][ps],ans[ps][pt],ans[pt][v]});
}
}
放几个例题,都很板。
【模板】最小割树(Gomory-Hu Tree)
[ZJOI2011] 最小割
[CERC2015]Juice Junctions
All Pairs Maximum Flow
曼哈顿计划EX
[CQOI2016] 不同的最小割
CF343E Pumping Stations