1. 程式人生 > 其它 >SAP OPEN UI5 Step6 Modules

SAP OPEN UI5 Step6 Modules

UPD 2021.6.1.12:50 @滑翔翼

我過了!!!感動中國!!!11

1.前置演算法

  1. kruskal 求 最小生成樹

  2. 倍增 求 LCA

2.定義

次小生成樹,顧名思義,邊權值和大於等於最小生成樹的邊權和且邊權和最小的生成樹

而嚴格次小生成樹,就是邊權值和嚴格大於最小生成樹的邊權和且邊權和最小的生成樹

3.正文

Part.0 一個栗子

這個圖的最小生成樹如下:

次小生成樹如下:

這張圖是不是符合一點:存在一個嚴格次小生成樹與最小生成樹只有一邊只差?

Part.1 定理 & 證明

事實上我剛才說的那玩意兒就是個定理

定理:總有至少一棵嚴格次小生成樹與最小生成樹只有一邊之差

接下來是證明:然鵝我只會口胡

開始口胡((

考慮反證法:

由於這顆嚴格次小生成樹與最小生成樹不同的邊最少有兩條,不妨設:

\(a,b\) 為最小生成樹裡的與最小生成樹不相同的兩條邊且 \(w_a\le w_b\)

\(c,d\) 為嚴格次小生成樹裡對應的兩條邊且 \(w_c\le w_d\)

\(\textsf1.\;\)如果 \(w_c\lt w_b\),我們可以再最小生成樹中邊 \(b\) 代替為 \(c\),這樣會得到一棵更小的生成樹,與最小生成樹的定義矛盾。

\(\textsf2.\;\)如果 \(w_c\gt w_b\),我們可以把最小生成樹中邊 \(b\) 代替為 \(c\)

,這樣會得到一棵邊權之和大於最小生成樹,小於嚴格次小生成樹的生成樹,與嚴格次小生成樹的定義矛盾。

\(\textsf3.\;\)如果 \(w_c=w_b\) ,我們可以最小生成樹中的邊 \(a\) 代替為 \(c\),就再分成情況 \(w_c\lt w_a\)\(w_c\gt w_a\) 來討論。和情況 \(\textsf{1},\textsf{2}\) 一樣。

\(\textsf4.\;\)如果\(w_a=w_b=w_c\lt w_d\),這時可以把邊 \(a\) 代替嚴格次小生成樹中的 \(c\) 邊,就符合定義了。

\(\textsf5.\;\)最後還剩一種情況 \(w_a=w_b=w_c=w_d\)

,則與嚴格次小生成樹定義中的嚴格矛盾

\(\textsf{Q.}\) 為什麼一定能代替呢?

\(\textsf{A.}\) 因為兩棵樹只有兩條邊不同,所以如果 \(b\) 不能換為 \(c\) ,當且僅當邊 \(a\) 和邊 \(c\) 是重邊。這時就可以用 \(a\) 代替 \(c\) 。如果邊 \(a\) 也不能替換 \(b\),則存在 \(a,b,c\) 都是重邊,與 \(a,b\) 存在同一個生成樹矛盾。


Part.2 思路

通過證明,不難想到如下方法:

列舉每一條不在最小生成樹的邊 \(\{u,v,w\}\),將其加入最小生成樹,這時會出現一個環。把加上邊之前環上的最大邊 \(\{x,y,z\}\) 丟掉。

設最小生成樹的邊權值和為 \(sum\) ,此時的新邊權值和為 \(sum-w+z\),就可以用 \(sum-w+z\) 更新答案。

圖文結合:考慮一個圖的一棵生成樹如下:

我們加上一條邊 \(\{5,6\}\)

產生一個環,應該是 \(5\to6\to3(lca)\to5\)

如果我們想求加上這條邊之前的最大值,就等價於求 \(5\to3(lca)\)\(6\to3(lca)\) 兩條路徑上的最大值,可以考慮倍增(具體倍增解法見 Part.3)

然後呢,你就會發現這樣只能求出來非嚴格次小生成樹

為什麼呢?因為如果 \(w\) 正好等於 \(z\) ,就不滿足嚴格條件了。

所以,在存一條最大邊的同時,還要存一條嚴格次大邊,用來處理兩條邊長度相等的情況。


Part.3 實現

實現分成兩部分:倍增的預處理函式以及單次詢問的處理


預處理

三個陣列如下。

  • 祖先陣列 \(fa_{i,j}\) 是點 \(i\)\(2^j\) 級祖先

  • 最大值陣列 \(max1_{i,j}\) 是點 \(i\) 到它的 \(2^j\) 級祖先這樣一條路徑上的權值最大的邊的權值

  • 最大值陣列 \(max2_{i,j}\) 是點 \(i\) 到它的 \(2^j\) 級祖先這樣一條路徑上的權值嚴格次大的邊的權值

根據倍增的更新方法:fa[i][j]=fa[fa[i,j-1]][j-1],同理,max1[i][j],max2[i][j] 也是由 i,fa[i][j-1]\(max1,max2\) 更新出來的

那我們怎麼更新 \(max1_{i,j}\)\(max2_{i,j}\) 呢?

定義 \(A=i,B=fa_{i,j-1},C=fa_{i,j}\)

為了方便看,用 \(max(u,v)\) 記錄 \(u\to v\) 的最大邊,\(mx(u,v)\) 記錄 \(u\to v\) 的嚴格次大邊

分三種情況討論


  1. \(max(a,b) = max(b,c)\)

顯然 \(max(a,c)=max(a,b)=max(b,c)\)

同理,\(mx(a,c)=\max(mx(a,b),mx(b,c))\)

  1. \(max(a,b) \lt max(b,c)\)

此時 \(max(a,c)=max(a,b)\)

\(mx(a,c)=max(b,c) \gets\) 其實就是 \(max\) 不要的那個

  1. \(max(a,b) \gt max(b,c)\)

和情況2差不多。

\(max(a,c)=max(b,c)\)

\(mx(a,c)=max(a,b)\)

\(\tt{Ps:}\) 實現時把情況2和3合併起來寫

\(\tt{Pss:}\) 實現時 DFS 可能會超時,要用 BFS(不過似乎模板題都不會


查詢

思路里講的差不多了?

事實上,對於可以輕鬆寫出 倍增LCA 的神仙,最大的難點就是如何更新答案(嚴格次小很毒瘤的

假如我想用 \(max1_{i,j},max2_{i,j}\) 來更新答案 \(me1,me2\),程式如下:(我用 \(x\) 代替 \(max1_{i,j},max2_{i,j}\))

if(x > me1) me2 = me1,me1 = x; // 注意me2也要更新
if(x > me2 && x != me1) me2 = x; // 注意要判x!=me1,不然me2會被更新為x
// Ps:me1 = first max edge,me2 = second max edge。。

注意程式第二行,有人肯定會問,寫成 else if(x > me2) 行不行?

考慮一下,如果 \(x=me1\),這時 \(me2\) 也會更新為 \(x\)\(me2\) 的確是次大值,但不符合嚴格次大值

所以還是有坑點的 qwq

\(\tt{Ps}\):倍增最後求出來的 LCA 實際上是 fa[x][0] ,所以最後還要用 max1[x][0],max2[x][0],max1[y][0],max2[y][0] 更新答案。


Part.4 程式碼

程式中 \(anc\) 其實是倍增的祖先陣列 \(fa\),因為 \(fa\) 與並查集求最小生成樹重名了 /kk

原題 P4180

上程式碼:

#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
#define int long long // 不開_____見祖宗
inline int Read(){
	register int x = 0,c = getchar();
	for(;c < 48 || c > 57;c = getchar());
	for(;c >= 48 && c <= 57;c = getchar()) x = x * 10 + (c ^ 48);
	return x;
}
const int maxn = 1e5 + 1;
const int maxm = 3e5 + 1;
const int maxlg = 25;
const int inf = 1e16 + 1; // inf 要開大
int n,m;
struct SMST{ // SMST → Second MST (蒟蒻的英文太菜了
	void init(){
		ecnt = lg2[1] = 0;me1 = me2 = -inf;fa[1] = 1;
		for(int i = 2;i <= n;++i) fa[i] = i,G[i].clear(),lg2[i] = lg2[i >> 1] + 1;
		memset(dep,0,sizeof dep),memset(max1,0,sizeof max1),memset(max2,0,sizeof max2),memset(anc,0,sizeof anc);
		while(!q.empty()) q.pop(); 
	}
    void add_edge(int u,int v,int w){
    	E[++ecnt] = (EDGE){u,v,w,false};
	}
    int fnd(int x){
		return x == fa[x] ? x : fa[x] = fnd(fa[x]);
	}
	int kruskal(){
		int sum = 0;
		std::sort(E + 1,E + m + 1);
		for(int i = 1;i <= m;++i){
			int fu = fnd(E[i].u),fv = fnd(E[i].v);
			if(fu != fv){
				fa[fv] = fu,sum += E[i].w,E[i].vis = true;
				G[E[i].u].push_back((edge){E[i].v,E[i].w}),G[E[i].v].push_back((edge){E[i].u,E[i].w});
				// G是最小生成樹
			}
		} 
		return sum;
	}
	void bfs(int st){
		dep[st] = 0;
		q.push(st);
		while(!q.empty()){
			int u = q.front();
			q.pop();
			for(int i = 0;i < G[u].size();++i){
				int v = G[u][i].v,w = G[u][i].w;
				if(v != anc[u][0]){
					dep[v] = dep[u] + 1;
					anc[v][0] = u,max1[v][0] = w,max2[v][0] = -inf;
					q.push(v);
					for(int i = 1;i <= lg2[dep[u]];++i){
						anc[v][i] = anc[anc[v][i - 1]][i - 1];
						if(max1[v][i - 1] != max1[anc[v][i - 1]][i - 1]){
							max1[v][i] = std::max(max1[v][i - 1],max1[anc[v][i - 1]][i - 1]);
							max2[v][i] = std::min(max1[v][i - 1],max1[anc[v][i - 1]][i - 1]);
						} else {
							max1[v][i] = max1[v][i - 1];
							max2[v][i] = std::max(max2[v][i - 1],max2[anc[v][i - 1]][i - 1]); // 分情況處理
						}
					}
				}
			}
		} 
	}
	void upd(int u,int lg){ // 把更新寫成函式
		int num = max1[u][lg];
		if(num > me1) me2 = me1,me1 = num;
		else if(num > me2 && num != me1) me2 = num;
		num = max2[u][lg];
		if(num > me1) me2 = me1,me1 = num;
		else if(num > me2 && num != me1) me2 = num;
	}
	void lca(int x,int y){
		me1 = me2 = -inf;
		if(dep[x] < dep[y]) std::swap(x,y);
		while(dep[x] > dep[y]){
			int d = lg2[dep[x] - dep[y]];
			upd(x,d),x = anc[x][d];
			// 向上跳要更新答案
		}
		if(x == y) return;
		for(int i = lg2[dep[x]];i >= 0;--i) if(anc[x][i] != anc[y][i]) upd(x,i),upd(y,i),x = anc[x][i],y = anc[y][i];
		upd(x,0),upd(y,0);
		// 由於最後求出來的LCA是x的父親,所以還要再更新一次
	}
	int smst(){
		int sum = kruskal(),ans = inf;
		bfs(1);
		for(int i = 1;i <= m;++i)
			if(!E[i].vis){
				lca(E[i].u,E[i].v);
				if(me1 != E[i].w) ans = std::min(ans,sum - me1 + E[i].w);
				else ans = std::min(ans,sum - me2 + E[i].w); // 注意判斷是不是等於原來的最大邊
			}
		return ans;
	}
	int ecnt,fa[maxn],dep[maxn],lg2[maxn],anc[maxn][maxlg];
	int max1[maxn][maxlg],max2[maxn][maxlg],me1,me2;// me1是本次詢問的最大值,me2是次大值
	struct EDGE{
		int u,v,w;
		bool vis; // vis 表示這條邊有沒有在最小生成樹中出現
		bool operator<(const EDGE& KlsAKsshForever) const {
			return w < KlsAKsshForever.w; // 。。。
		}
	} E[maxm];
	struct edge{
		int v,w;
	};
	std::vector<edge> G[maxn];
	std::queue<int> q;
} smst; // 原諒結構體清奇馬蜂
signed main(){
	n = Read(),m = Read();
	smst.init();
	for(int i = 1;i <= m;++i){
		int u,v,w;
		u = Read(),v = Read(),w = Read();
		smst.add_edge(u,v,w);
	}
	printf("%lld\n",smst.smst());
	return 0;
}
完結撒花qwq

希望更壓行的馬蜂?使用滑翔翼

4.總結

個人覺得嚴格次小生成樹最毒瘤的地方就是它是嚴格次小,細節挺多,kruskal 挺簡單的,倍增的 bfs ,查詢應該也不難,最難我覺得是 \(max1,max2\) 的更新,以及嚴格次小的查詢。