专门开个博客一是因为没地放了,二是以后次小生成树什么的就一块扔这了。
点数n,边数m的图的最小生成树大概有两个算法:
思路非常简单粗暴,把所有边扔出来按照边权排个序,然后拿并查集维护点的连通关系,最后选出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; }
这个是每次维护最小生成树的一部分边,类似Dijkstra。(这玩意真没啥意思)
同样的,用堆优化可以到\(O(mlogn)\),但是你还不如直接写kruskal。
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; }
首先定义最小边是一个连通块向其他连通块连的边中边权最小的一个。这个算法的大体思路是初始将每个点视作一个连通块,通过最小边合并连通块(共\(\log n\)次合并),最终形成最小生成树。这个东西一般不会让你写,但是不太好建边的时候可能有奇效(比如这道题)。
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"); }