最小生成树综合

最小生成树综合


前言:

  本博客记录一下最小生成树及其拓展问题。定义和求法很少说了。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更小。