最小生成树综合
前言:
本博客记录一下最小生成树及其拓展问题。定义和求法不多说了。
例题:
特殊边条数限制生成树:
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。题目保证有解。
分析:
二分答案,每次给所有的白边权值加上一个mid,白条条数>=need就提高下界,<need就降低上界。最后给权值之和减去need * mid;
特殊生成树模型:
魔术师的桌子上有n个杯子排成一行,编号为1,2,…,n,其中某些杯子底下藏有一个小球,如果你准确地猜出是哪些杯子,你就可以获得奖品。花费c_ij元,魔术师就会告诉你杯子i,i+1,…,j底下藏有球 的总数的奇偶性。
采取最优的询问策略,你至少需要花费多少元,才能保证猜出哪些杯子底下藏着球
分析:
为了知道每一个的奇偶性,我们必须知道每相邻两个的奇偶性,且信息具有传递性,我们知道[ a,b)和(b,c]后,也就知道了[ a,c ]
每个点是一个节点,每次询问是一条边,所有点联通的时候问题解决,跑最小生成树。
严格次小生成树:
求严格次小生成树,保证有解。
分析:
先求一遍最小生成树,然后枚举没有用到的边,若将没有用到的边连上必定成环,那么就用树上倍增找到最大的可以替换的(权值小于当前边权值的边)换掉,每次统计换掉后的答案。
为了保证找到的是权值小于当前边的最大权值的边,我们需要倍增地记录一个最大值和一个次大值。
#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
",res);
return 0;
}
判断最小生成树唯一性:
给定无向图,判断最小生成树唯一性。
显然可以用刚才求次小生成树的方法,倍增判断环上最大值是否等于替换边权值
有没有更好的解法呢?
有的qwq
最小生成树唯一不唯一,与等边权边能否互换有关系
把所有边按边权分组。
我们在克鲁斯卡尔的过程中,开两个冰茶几记录,第一个冰茶几负责记录最小生成树,第二个冰茶几把所有边权小于当前边的两端的点全部合并
如果我们在后面发现了当前枚举的一条边两端的点在第一套冰茶几中已经被合并那么查看第二套冰茶几:
如果已经合并,说明是更小的边合并了他们,此时最小生成树唯一;
如果还没有合并,说明是等长的边合并了他们,此时最小生成树不唯一;
顶点度数限制MST:
给定一张N个点M条边的无向图,求出无向图的一棵最小生成树,满足一号节点的度数不超过给定的整数k
先忽略1号节点跑一遍MST,然后将1号节点与每一个联通块用最小边合并,这时会获得一个最小生成树。
然后枚举每一条与1号节点相连的边,用次小生成树思想判断能不能断开另一条边使得MST变小,直到1号节点度数达到k或者不能使MST更小。