次小生成樹
次小生成樹
匯入:次小生成樹是在最小生成樹的基礎上求得的,是noip提高組常考點之一,
有著較高的靈活性。
演算法簡介:
學習次小生成樹之前你得先弄明白最小生成樹,假設有一n個點m條邊的無向
圖,求次小生成樹基本步驟如下:
1.求出最小生成樹,並將所有樹邊建樹。
2.依次列舉非樹邊,並將其加入樹上,形成一個環。
3.在環上找到一邊滿足:除非樹邊外最大(如果要求求嚴格次小生成樹還需
滿足邊權小於新加入的非樹邊)。
4.刪去該邊,求出樹的邊權合。
5.重複2.3.4。
正確性證明:
將最小生成樹的邊權和記為S,非樹邊邊權記為V,除樹邊外最大邊權記為U,此時
生成樹的邊權為S-U+V,假設存在一邊邊權記為Q使得S-Q+V<S-U+V則必須滿足
Q>U與假設不符,即選擇U為最優方案。
這個演算法乍一看,求最小生成樹O(mlogm)列舉非樹邊O(m)找到滿足條
件的邊O(m)總的時間複雜度為O(mlogm+m*m)只適用於(m<5000)接下來我們
對演算法進行優化,由於OI比賽中一般考察嚴格次小生成樹,我們以嚴格次小為
例來討論。
由於最小生成樹求法我們基於已有最優演算法,不再考慮優化,O(m)列舉顯
然無法優化因此我們只考慮尋找可行邊的優化。假設我們現在有一張圖求出的
最小生成樹如下
下面我們加入一條邊(12,15)
此時形成一個環我們要在這個環上尋找可行邊,不妨將其以最近公共祖先
為界分為兩部分,變成兩條鏈,那問題便成為了在兩條鏈上求最大值和次大值
(記錄次大值防止最大值與非樹邊邊權一樣時求出的不是嚴格次小生成樹)
那麼我們可以使用倍增進行求解,由於最近公共祖先也需使用倍增求解,
我們可以在求最近公共祖先的時候同時求解出最大值次大值。複雜度從O(m)降
為O(logm)
ac程式碼:
#include<bits/stdc++.h> #define N 500010 #define inf 0x3f3f3f3f3f3f3f3f using namespace std; struct node { int f,t,v; bool friend operator <(const node a,const node b) { return a.v<b.v; } }ee[N],dl[N]; int head[N],to[N],net[N],vis[N],cnt; int f[21][N],v[2][21][N],d[N]; int n,m; long long ans=inf,sum; int fa[N]; int num; void add(int from,int t,int v) { net[++cnt]=head[from]; to[cnt]=t; vis[cnt]=v; head[from]=cnt; } int found(int x) { if(fa[x]!=x)return fa[x]=found(fa[x]); return fa[x]; } void kul() { for(int i=1;i<=n;i++)fa[i]=i; for(int i=1;i<=m;i++) { node e=dl[i]; int fx=found(e.f); int fy=found(e.t); if(fx!=fy) { add(e.f,e.t,e.v); add(e.t,e.f,e.v); fa[fx]=fy; sum+=e.v; } else ee[++num]=e; } } void LCA(int fa,int x,int vv) { d[x]=d[fa]+1; f[0][x]=fa; v[0][0][x]=vv; for(int i=1;i<=16;i++) { f[i][x]=f[i-1][f[i-1][x]]; v[0][i][x]=max(v[0][i-1][x],v[0][i-1][f[i-1][x]]); int q[]={v[0][i-1][x],v[0][i-1][f[i-1][x]],v[1][i-1][x],v[1][i-1][f[i-1][x]]}; sort(q,q+4); for(int j=0;j<4;j++) if(q[j]!=v[0][i][x])v[1][i][x]=q[j]; } for(int i=head[x];i;i=net[i]) if(to[i]!=fa) LCA(x,to[i],vis[i]); } void lca(node e) { int q[N]; int t=0; int x=e.f,y=e.t,vi=e.v; if(d[x]<d[y])swap(x,y); for(int i=16;i>=0;i--) if(d[f[i][x]]>=d[y]) q[++t]=v[0][i][x],q[++t]=v[1][i][x],x=f[i][x]; if(x!=y) { for(int i=16;i>=0;i--) if(f[i][x]!=f[i][y]) { q[++t]=v[0][i][x],q[++t]=v[0][i][y]; q[++t]=v[1][i][x],q[++t]=v[1][i][y]; x=f[i][x],y=f[i][y]; } q[++t]=v[0][0][x]; } sort(q+1,q+t+1); int vv; for(int i=t;i;i--) if(vi!=q[i]) { vv=q[i]; break; } ans=min(ans,sum+vi-vv); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int x,y,v; scanf("%d%d%d",&x,&y,&v); dl[i]={x,y,v}; } sort(dl+1,dl+m+1); kul(); LCA(0,1,0); for(int i=1;i<=num;i++) lca(ee[i]); cout<<ans; return 0; }