1. 程式人生 > 其它 >[Asta's Legacy]兩個和圖論有關的小東西

[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\)

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