SAP OPEN UI5 Step6 Modules
UPD 2021.6.1.12:50 @滑翔翼
我過了!!!感動中國!!!11
1.前置演算法
-
kruskal 求 最小生成樹
-
倍增 求 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\) 的嚴格次大邊
分三種情況討論
- \(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))\)
- \(max(a,b) \lt max(b,c)\)
此時 \(max(a,c)=max(a,b)\)
而 \(mx(a,c)=max(b,c) \gets\) 其實就是 \(max\) 不要的那個
- \(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
上程式碼:
#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\) 的更新,以及嚴格次小的查詢。