最小生成樹很好求,那么對於次小生成樹要怎么求呢?
稍加思考,我們可以想到,次小生成樹與最小生成樹差的只是一條邊。
為什么呢?我們先建出一棵最小生成樹,滿足使用的邊都是最小的,剩下的邊(稱為非樹邊)一定沒有樹邊優。如果我們加入一條非樹邊,刪除最小生成樹中的一條邊,次小生成樹一定是包括在以這種方法建出的樹中的(倘若刪兩條樹邊加兩條非樹邊,則肯定沒有刪一條加一條優,絕不是次小生成樹)
於是,我們可以這樣操作:
對於每一條非樹邊,其邊的兩端點為x,y。將其加入到最小生成樹中,則一定會構成一個環
如果這時刪除這個環中的最大的邊(當然不能是插入的邊)就構成了必須加入紅色邊的最小生成樹。
注意:一定是刪除環中最大的邊,這樣才保證新生成的樹與原來的樹相差最小,才可能為次小生成樹。
刪去--->
仍然是一棵樹,且是必須加入紅色邊的最小生成樹,則有可能是次小生成樹
現在差不多已經明白了思想,就要看怎么實現了。得知了紅色邊的端點x,y,我們就可以找到它所在的環,觀察圖,發現從x到lca,再到y就構成了它所在的環。於是實現的難點就在於怎么快速的找到某個環內最大的邊。
也就是說,尋找從x到y這條路徑上的最大邊(還沒加入紅色邊呢,還沒成環,因此看路徑)
問題轉化成:尋找樹上某條路徑上的最大邊。
這樣實現就比較好想了,我們可以用樹上倍增處理處樹上路徑的最大邊:maxx[x][i]=max(maxx[f[x][i-1]]][i-1],maxx[x][i-1]),maxx存的是最大路徑值。
求(x,y)的路徑最大值就從x跳到lca,從y跳到lca,兩者取個max就行。
但是,你以為這樣這道題就結束了嗎?QAQ
我們再看題,發現題目是嚴格次小生成樹,也就是說,新生成的樹一定得比最小生成樹大,而不是大於等於。如果加入的紅邊的長度等於刪去的邊的長度就。。。wawawa(題中並沒有說所以邊的長度不同哦)
於是針對這種情況,我們還要再考慮考慮怎么辦。
我們可以這樣處理,對於樹上的路徑,我們不僅維護一個最大邊,還維護一個次大邊,這樣當最大邊等於紅邊時,不可以刪最大邊了,我們還可以刪去次大邊。
那么在倍增的時候注意一下維護細節就好。

for(int j=1;j<=20;++j) { if(dep[v]<(1<<j))break;//注意:如果深度小於向上走的步數就可以break掉了 f[v][j]=f[f[v][j-1]][j-1];//f是向上走到達的點 max1[v][j]=max(max1[v][j-1],max1[f[v][j-1]][j-1]);//max1是最大邊 if(max1[v][j-1]==max1[f[v][j-1]][j-1]) g[v][j]=max(g[v][j-1],g[f[v][j-1]][j-1]);//g是次大邊 else { g[v][j]=min(max1[v][j-1],max1[f[v][j-1]][j-1]); g[v][j]=max(g[v][j],g[f[v][j-1]][j-1]); g[v][j]=max(g[v][j-1],g[v][j]); } }
放出完整代碼

#include<bits/stdc++.h> #define INF 2100000001 #define M 300003 #define N 100003 #define LL long long using namespace std; int read() { int f=1,x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} return x*f; } struct EDGE{ int x,y,z,flagg; }w[M]; struct edgee{ int to,nextt,val; }e[M]; int tot=0,m,n,minn=INF; LL ans=0; int f[N][22],max1[N][22],g[N][22],fa[N],head[N],dep[N]; bool cmp(const EDGE &a,const EDGE &b) { return a.z<b.z; } int getfa(int x) { if(fa[x]==x) return x; return fa[x]=getfa(fa[x]); } void add(int a,int b,int v) { tot++; e[tot].to=b; e[tot].nextt=head[a]; e[tot].val=v; head[a]=tot; } void kruscal() { int q=1; sort(w+1,w+m+1,cmp); for(int i=1;i<=n;++i) fa[i]=i; for(int i=1;i<=m;++i) { int s1=getfa(w[i].x); int s2=getfa(w[i].y); if(s1!=s2) { ans+=w[i].z;w[i].flagg=1; q++; fa[s1]=s2; add(w[i].x,w[i].y,w[i].z); add(w[i].y,w[i].x,w[i].z); } if(q==n) break; } } void dfs(int x) { for(int i=head[x];i;i=e[i].nextt) { int v=e[i].to; if(v==f[x][0]) continue; f[v][0]=x; max1[v][0]=e[i].val; dep[v]=dep[x]+1; for(int j=1;j<=20;++j) { if(dep[v]<(1<<j))break;//注意:如果深度小於向上走的步數就可以break掉了 f[v][j]=f[f[v][j-1]][j-1];//f是向上走到達的點 max1[v][j]=max(max1[v][j-1],max1[f[v][j-1]][j-1]);//max1是最大邊 if(max1[v][j-1]==max1[f[v][j-1]][j-1]) g[v][j]=max(g[v][j-1],g[f[v][j-1]][j-1]);//g是次大邊 else { g[v][j]=min(max1[v][j-1],max1[f[v][j-1]][j-1]); g[v][j]=max(g[v][j],g[f[v][j-1]][j-1]); g[v][j]=max(g[v][j-1],g[v][j]); } } dfs(v); } } int LCA(int u,int x) { if(dep[u]<dep[x])swap(u,x); for(int i=20;i>=0;--i)if(dep[f[u][i]]>=dep[x])u=f[u][i]; if(x==u) return x; for(int i=20;i>=0;--i)if(f[x][i]!=f[u][i])x=f[x][i],u=f[u][i]; return f[x][0]; } void change(int x,int lca,int val) { int maxx1=0,maxx2=0; int d=dep[x]-dep[lca]; for(int i=0;i<=20;++i) { if(d<(1<<i))break; if(d&(1<<i)) { if(max1[x][i]>maxx1) { maxx2=max(maxx1,g[x][i]); maxx1=max1[x][i]; } x=f[x][i]; } } if(val!=maxx1) minn=min(minn,val-maxx1); else minn=min(minn,val-maxx2); } void work() { for(int i=1;i<=m;++i) { if(!w[i].flagg) { int s1=w[i].x,s2=w[i].y; int lca=LCA(s1,s2); change(s1,lca,w[i].z);change(s2,lca,w[i].z); } } } int main() { n=read();m=read(); for(int i=1;i<=m;++i) { w[i].x=read();w[i].y=read();w[i].z=read(); } kruscal(); // printf(">>%d\n",ans); dfs(1); work(); printf("%lld\n",ans+minn); } /* 5 7 1 3 1 1 4 10 3 4 2 3 2 3 2 4 7 1 5 1 3 5 19 */
寫了好久終於寫完了(撒花~✿✿ヽ(°▽°)ノ✿)