最小生成樹綜合

最小生成樹綜合


前言:

  本博客記錄一下最小生成樹及其拓展問題。定義和求法很少說了。php


 


例題:

  特殊邊條數限制生成樹:

  洛谷P2619c++

  給你一個無向帶權連通圖,每條邊是黑色或白色。讓你求一棵最小權的剛好有need條白色邊的生成樹。題目保證有解。spa

  分析:code

  二分答案,每次給全部的白邊權值加上一個mid,白條條數>=need就提升下界,<need就下降上界。最後給權值之和減去need * mid;blog

  特殊生成樹模型:

  BZOJ3714get

  魔術師的桌子上有n個杯子排成一行,編號爲1,2,…,n,其中某些杯子底下藏有一個小球,若是你準確地猜出是哪些杯子,你就能夠得到獎品。花費c_ij元,魔術師就會告訴你杯子i,i+1,…,j底下藏有球 的總數的奇偶性。
  採起最優的詢問策略,你至少須要花費多少元,才能保證猜出哪些杯子底下藏着球
博客

  分析:it

  爲了知道每個的奇偶性,咱們必須知道每相鄰兩個的奇偶性,且信息具備傳遞性,咱們知道[ a,b)和(b,c]後,也就知道了[ a,c ]class

  每一個點是一個節點,每次詢問是一條邊,全部點聯通的時候問題解決,跑最小生成樹。方法

  嚴格次小生成樹:

  洛谷P4180

  求嚴格次小生成樹,保證有解。

  分析:

  先求一遍最小生成樹,而後枚舉沒有用到的邊,若將沒有用到的邊連上一定成環,那麼就用樹上倍增找到最大的能夠替換的(權值小於當前邊權值的邊)換掉,每次統計換掉後的答案。

  爲了保證找到的是權值小於當前邊的最大權值的邊,咱們須要倍增地記錄一個最大值和一個次大值。

  

#include<bits/stdc++.h>
using namespace std; #define int long long inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } int n,m,tot,sum,res; int f[100010]; int bf[100010][21]; int val[100010][21]; int cval[100010][21]; int lg[100010],dep[100010]; struct point { int nxt,to,val,vis; }e[1000010],a[1000010]; int head[100010],cnt; inline void add(int x,int y,int z) { a[++cnt].nxt=head[x]; a[cnt].to=y; a[cnt].val=z; head[x]=cnt; } inline bool cmp(point a,point b) { return a.val<b.val; } inline int find(int k) { if(f[k]==k||!f[k]) return k; return f[k]=find(f[k]); } inline void dfs(int now,int fa,int deep,int v) { dep[now]=deep; bf[now][0]=fa; val[now][0]=v; cval[now][0]=-1e15; for(int i=1;(1<<i)<=deep;++i) { bf[now][i]=bf[bf[now][i-1]][i-1]; val[now][i]=max(val[now][i-1],val[bf[now][i-1]][i-1]); cval[now][i]=max(cval[now][i-1],cval[bf[now][i-1]][i-1]); if(val[now][i-1]>val[bf[now][i-1]][i-1]) { cval[now][i]=max(cval[now][i],val[bf[now][i-1]][i-1]); } else if(val[now][i-1]<val[bf[now][i-1]][i-1]) { cval[now][i]=max(cval[now][i],val[now][i-1]); } } for(int i=head[now];i;i=a[i].nxt) { int t=a[i].to; if(t==fa) continue; dfs(t,now,deep+1,a[i].val); } } inline void query(int x,int y,int v) { int ans=0; if(dep[x]<dep[y]) swap(x,y); while(dep[x]>dep[y]) { int d=dep[x]-dep[y]; if(v^val[x][lg[d]-1]) ans=max(ans,val[x][lg[d]-1]); else ans=max(ans,cval[x][lg[d]-1]); x=bf[x][lg[d]-1]; } if(x==y) { res=min(res,sum-ans+v); return ; } for(int i=lg[dep[x]]-1;i>=0;--i) { if(bf[x][i]^bf[y][i]) { int tx=(v^val[x][i])?val[x][i]:cval[x][i]; int ty=(v^val[y][i])?val[y][i]:cval[y][i]; ans=max(ans,max(tx,ty)); x=bf[x][i],y=bf[y][i]; } } int tx=(v^val[x][0])?val[x][0]:cval[x][0]; int ty=(v^val[y][0])?val[y][0]:cval[y][0]; ans=max(ans,max(tx,ty)); res=min(res,sum-ans+v); } signed main() { n=read(),m=read(); for(int i=1;i<=m;++i) { e[i].nxt=read(),e[i].to=read(),e[i].val=read(); } sort(e+1,e+m+1,cmp); for(int i=1;i<=m;++i) { int tx=find(e[i].nxt),ty=find(e[i].to); if(tx==ty) continue; f[tx]=ty; sum+=e[i].val; e[i].vis=1; add(e[i].nxt,e[i].to,e[i].val); add(e[i].to,e[i].nxt,e[i].val); if(++tot==n-1) break; } dfs(1,0,1,0); for(int i=1;i<=n;++i) lg[i]=lg[(i>>1)]+1; lg[0]=1; res=1e15; for(int i=1;i<=m;++i) { if(e[i].vis) continue; query(e[i].nxt,e[i].to,e[i].val); } printf("%lld\n",res); return 0; }

  

  判斷最小生成樹惟一性:

  POJ1679

  給定無向圖,判斷最小生成樹惟一性。

  顯然能夠用剛纔求次小生成樹的方法,倍增判斷環上最大值是否等於替換邊權值

  有沒有更好的解法呢?

  有的qwq

  最小生成樹惟一不惟一,與等邊權邊可否互換有關係

  把全部邊按邊權分組。

  咱們在克魯斯卡爾的過程當中,開兩個冰茶几記錄,第一個冰茶几負責記錄最小生成樹,第二個冰茶几把全部邊權小於當前邊的兩端的點所有合併

  若是咱們在後面發現了當前枚舉的一條邊兩端的點在第一套冰茶几中已經被合併那麼查看第二套冰茶几:

  若是已經合併,說明是更小的邊合併了他們,此時最小生成樹惟一;

  若是尚未合併,說明是等長的邊合併了他們,此時最小生成樹不惟一;

  頂點度數限制MST:

  POJ1639

  給定一張N個點M條邊的無向圖,求出無向圖的一棵最小生成樹,知足一號節點的度數不超過給定的整數k

  先忽略1號節點跑一遍MST,而後將1號節點與每個聯通塊用最小邊合併,這時會得到一個最小生成樹。

  而後枚舉每一條與1號節點相連的邊,用次小生成樹思想判斷能不能斷開另外一條邊使得MST變小,直到1號節點度數達到k或者不能使MST更小。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息