瞭解次小生成樹(嚴格次小生成樹) 之前,需要先了解前置知識。
最小生成樹:https://blog.csdn.net/A_Pathfinder/article/details/88125032
最近公共祖先 :https://blog.csdn.net/A_Pathfinder/article/details/101034332
首先我們看什麼是嚴格,我們求得最小生成樹準確來說是不嚴格最小生成樹
因爲在最小生成樹的定義求法中,對於任何的u,v有u到v之間邊權最大值 ‘ 小於等於 ’ u到v未選入的邊的邊權,所以說,不嚴格次小生成樹只要
遍歷每條未選的邊(u,v,d),用它替換u和v之間的最大邊即可,而再嚴格的定義中,是不可以有等於的,所以我們需要保存一個次大值。
對於具體求法,我們先找出一顆最小生成樹,設邊權和爲sum,如果我們把一條非樹邊(x,y,z)添加到最小生成樹中,會與樹上x,y之間的路徑形成一個環,找到x,y路徑上的最大邊權val1,嚴格次大邊權(第二大)爲val2,(val1 > val2)。
若z > val1,則把val1對應的那條邊換成(x,y,z)這條邊,就得到一個嚴格次小生成樹的一個候選結果,邊權和爲sum-val1+z。
若z == val1,則把val2對應的那條邊換成(x,y,z)這條邊,就得到一個嚴格次小生成樹的一個候選結果,邊權和爲sum-val2+z。
枚舉每條非樹邊,添加到最小生成樹中,取最小的候選結果就是我們選出的嚴格次小生成樹。
那麼,我們解決的問題就變成了如何快速的求出一條路徑上的最大邊權與嚴格次大邊權。
這裏我們可以用倍增算法進行預處理:
void cal()
{
for(ll i=1;i<=18;++i)
for(ll j=1;j<=n;++j)
{
bz[j][i]=bz[bz[j][i-1]][i-1];
maxi[j][i]=max(maxi[j][i-1],maxi[bz[j][i-1]][i-1]);
mini[j][i]=max(mini[j][i-1],mini[bz[j][i-1]][i-1]);
if(maxi[j][i-1]>maxi[bz[j][i-1]][i-1])mini[j][i]=max(mini[j][i],maxi[bz[j][i-1]][i-1]);
else if(maxi[j][i-1]<maxi[bz[j][i-1]][i-1])mini[j][i]=max(mini[j][i],maxi[j][i-1]);
}
}
最後,我們考慮每條非樹邊,採用倍增LCA算法,找到對應該段路徑的最大邊權次大邊權,合併到答案中,即可得到我們要求的嚴格次小生成樹。
算法時間複雜度爲O(mlogn).
上模板題:
嚴格次小生成樹
1490:祕密的牛奶運輸
模板代碼
//採用了lg的常數優化:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5+7,M = 3e5 + 7,MM = 3e5+7;
const ll INF = 0x7ffffffffff;;
int n,m;
ll sum;
int cnt,head[MM],ver[MM],nex[MM],edge[MM];
int tree[MM],pre[N],ppre[N][23],depth[N],lg[N];
ll maxf[N][23],minf[N][23];////這兩個要和ans 作比較,所以用ll
struct E{
int from,to,w;
E(){}
E(int from,int to,int w) : from(from),to(to),w(w){}
bool operator < (const E &b)const{
return w < b.w;
}
}e[M];
void add(int x,int y,int w){
ver[++cnt] = y;
nex[cnt] = head[x];
edge[cnt] = w;
head[x] = cnt;
}
int find(int x){
return x == pre[x] ? x : pre[x] = find(pre[x]);
}
void read(){
scanf("%d%d",&n,&m);
for(int i=1;i <= m;i ++)
scanf("%d%d%d",&e[i].from,&e[i].to,&e[i].w);
for(int i=0; i < N; i++)
pre[i] = i;
}
void work1(){
sort(e+1,e+m+1);
for(int i=1;i <= m;i ++){
int x = e[i].from,y = e[i].to,w = e[i].w;
int fx = find(x), fy = find(y);
if(fx != fy){
pre[fx] = fy;
sum += w;
add(x,y,w);
add(y,x,w);
tree[i] = 1;
}
}
}
void dfs(int f,int fa,int w){
depth[f] = depth[fa] + 1;
ppre[f][0] = fa;
minf[f][0] = -INF;
maxf[f][0] = w;
for(int i=1; (1<<i) <= depth[f];i++){
ppre[f][i] = ppre[ppre[f][i-1]][i-1];
maxf[f][i] = max(maxf[f][i-1],maxf[ppre[f][i-1]][i-1]);
minf[f][i] = max(minf[f][i-1],minf[ppre[f][i-1]][i-1]);//這裏分清次小關係
if(maxf[f][i-1] > maxf[ppre[f][i-1]][i-1]) minf[f][i] = max(minf[f][i],maxf[ppre[f][i-1]][i-1]);
else if(maxf[f][i-1] < maxf[ppre[f][i-1]][i-1]) minf[f][i] = max(minf[f][i],maxf[f][i-1]);
}
for(int i=head[f]; i ; i=nex[i]){
int y = ver[i],w = edge[i];
if(y != fa){
dfs(y,f,w);
}
}
}
int lca(int x,int y){
if(depth[x] < depth[y]) swap(x,y);
while(depth[x] > depth[y])
x = ppre[x][lg[depth[x]-depth[y]] -1];
if(x==y) return x;
for(int i = lg[depth[x]]-1; i>=0; i--){
if(ppre[x][i] != ppre[y][i])
x= ppre[x][i],y = ppre[y][i];
}
return ppre[x][0];
}
ll qmax(int x,int y,int maxx){
ll ans = -INF;
for(int i = lg[depth[x]]-1;i>=0;i--){
if(depth[ppre[x][i]]>=depth[y]){
if(maxx != maxf[x][i]) ans = max(ans,maxf[x][i]);
else ans = max(ans,minf[x][i]);
x = ppre[x][i];
}
}
return ans;
}
void work2(){
for(int i=1;i <= n;i++)
lg[i] = lg[i-1] + (1<<lg[i-1]==i);
dfs(1,0,0);
ll ans = INF;
for(int i = 1;i <= m;i++){
if(tree[i]) continue;
int x = e[i].from,y = e[i].to,w = e[i].w;
int lc = lca(x,y);
ll maxx = qmax(x,lc,w);
ll maxv = qmax(y,lc,w);
ans = min(ans,sum-max(maxx,maxv)+w);//找到到公共父親節點路徑中最大的那條,替換到非樹邊。
}
printf("%lld\n",ans);
}
int main(){
read();
work1();
work2();
return 0;
}
參考文獻:
《信息奧賽一本通》
https://www.luogu.org/problemnew/solution/P4180