最小生成树、次小生成树、Kruskal重构树
专门开个博客一是因为没地放了,二是以后次小生成树什么的就一块扔这了。
最小生成树
点数n,边数m的图的最小生成树大概有两个算法:
- Kruskal算法(
)
思路非常简单粗暴,把所有边扔出来按照边权排个序,然后拿并查集维护点的连通关系,最后选出n-1条边。
int kruskal(int x){
sort(edge+1,edge+t+1,cmp);
chushihua();
int sum=0,cnt=0;
for(int i=1;i<=t;i++){
int x=edge[i].u,y=edge[i].v;
if(find(x)!=find(y)){
father[y]=x;
sum+=edge[i].w;
cnt++;
}
if(cnt==n-1)break;
}
return sum;
}
- Prim算法(
)
这个是每次维护最小生成树的一部分边,类似Dijkstra。(这玩意真没啥意思)
同样的,用堆优化可以到
int prim(int x){
int sum=0;memset(a,0x3f,sizeof(a));
for(int i=1;i<=n;i++)dis[i]=a[x][i];
v[x]=true;
for(int i=2;i<=n;i++){
int min=2147483647,k;
for(int j=1;j<=n;j++){
if(!v[j]&&dis[j]<min)min=dis[j];k=j;
}
sum+=dis[k];v[k]=true;//找一条与当前生成树相连的最小的边记录答案
for(int j=1;j<=n;j++){
if(!v[j]&&dis[j]>a[k][j])dis[j]=a[k][j];//更新边
}
}
return sum;
}
- Boruvka算法(听完林老师讲课看看原题题解看到的)(
)
首先定义最小边是一个连通块向其他连通块连的边中边权最小的一个。这个算法的大体思路是初始将每个点视作一个连通块,通过最小边合并连通块(共
int link[5010],val[5010];
void boruvka(int n){
int ans=0,num=0;
bool jud=true;
while(jud){
jud=false;
memset(link,0,sizeof(link));
memset(val,0x3f,sizeof(val));
for(int i=1;i<=n;i++){
int x=find(i);
for(int j=head[i];j;j=edge[j].next){
int y=find(edge[j].v);
if(val[x]>edge[j].w&&x!=y){//找到连通块i的最小边
val[x]=edge[j].w;//如果该边两端点不都属于连通块且边权更小则更新
link[x]=y;
}
}
}
for(int i=1;i<=n;i++){
int x=find(i);
if(find(i)==i){
if(link[x]&&x!=find(link[x])){
merge(x,link[x]);ans+=val[x];//连接最小边两端的两个连通块
jud=true;num++;//计入合并次数(洛谷的无解要特判一下)
}
}
}
}
if(num==n-1)printf("%d",ans);
else printf("orz");
}
还有一个小定理就是一张图的所有最小生成树的每种权值的边的数量是相同的。
关于oi-wiki上那个语焉不详的最小生成树的唯一性,实际上就是对于每个相同权值的边进行这样一个过程:
- 先扫一遍找到可能向最小生成树里加多少边(就是两端点属于的边集不同)
- 跑Kruskal,看实际加入了多少边
- 如果不相同则不唯一
次小生成树
首先最小生成树和次小生成树只差一条边。
- 非严格次小生成树:把最小生成树的每条边标记一下,然后对所有没标记的边
求出 路径上边权的最大值,用这条边替换,得到的所有答案的最小值就是非严格次小生成树。倍增即可,代码就不放了。 - 严格次小生成树:这个为了保证选的这条边和边权最大值不同还得记录一个次大值。这个刚写了放一下。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define int long long
using namespace std;
const int inf=0x3fffffff;
int n,m;
struct node{
int v,w,next;
}edge[600010];
struct E{
int u,v,w;
bool operator<(const E &s)const{
return w<s.w;
}
}g[300010];
int t,ans=__LONG_LONG_MAX__,sum,head[100010],f[100010];
bool v[300010];
int find(int x){
return x==f[x]?f[x]:f[x]=find(f[x]);
}
void add(int u,int v,int w){
edge[++t].v=v;edge[t].w=w;
edge[t].next=head[u];head[u]=t;
}
int dep[100010],fa[100010][20],mx[100010][20],se[100010][20];
void dfs(int x,int f){
dep[x]=dep[f]+1;fa[x][0]=f;se[x][0]=-inf;
for(int i=1;i<=__lg(dep[x]);i++){
fa[x][i]=fa[fa[x][i-1]][i-1];
int ret[4]={mx[x][i-1],mx[fa[x][i-1]][i-1],se[x][i-1],se[fa[x][i-1]][i-1]};
sort(ret,ret+4);
mx[x][i]=ret[3];
int p=2;
while(p>=0&&ret[p]==ret[3])p--;
se[x][i]=(p==-1?-inf:ret[p]);
}
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=f){
mx[edge[i].v][0]=edge[i].w;
dfs(edge[i].v,x);
}
}
}
int lca(int x,int y){
if(dep[x]>dep[y])swap(x,y);
for(int i=__lg(n);i>=0;i--)if(dep[fa[y][i]]>=dep[x])y=fa[y][i];
if(x==y)return x;
for(int i=__lg(n);i>=0;i--)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
int query(int x,int y,int val){
int ans=-inf;
for(int i=__lg(n);i>=0;i--){
if(dep[fa[x][i]]>=dep[y]){
if(val!=mx[x][i])ans=max(ans,mx[x][i]);
else ans=max(ans,se[x][i]);
x=fa[x][i];
}
}
return ans;
}
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++)scanf("%lld%lld%lld",&g[i].u,&g[i].v,&g[i].w);
sort(g+1,g+m+1);
for(int i=1;i<=n;i++)f[i]=i;
int cnt=0;
for(int i=1;i<=m;i++){
int x=find(g[i].u),y=find(g[i].v);
if(x!=y){
sum+=g[i].w;f[y]=x;
add(g[i].u,g[i].v,g[i].w);add(g[i].v,g[i].u,g[i].w);
v[i]=true;
}
}
dfs(1,0);
for(int i=1;i<=m;i++){
if(!v[i]){
int lc=lca(g[i].u,g[i].v);
int tmpa=query(g[i].u,lc,g[i].w),tmpb=query(g[i].v,lc,g[i].w);
if(max(tmpa,tmpb)!=-inf)ans=min(ans,sum-max(tmpa,tmpb)+g[i].w);
}
}
printf("%lld\n",ans);
return 0;
}
Kruskal重构树
实际上并不是什么很高深的东西。
考虑我们Kruskal的过程,我们每次选边的时候新建一个节点,它的权值是这条边的边权,左右儿子是这条边的两个端点,这样最后会出现一棵二叉树,就是Kruskal重构树。
一个重要性质是:原图中两个点之间的所有简单路径上最大边权的最小值 = 最小生成树上两个点之间的简单路径上的最大值 = Kruskal 重构树上两点之间的 LCA 的权值。可能有点绕,不过很好理解。
快踩
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App