总结:
- 当点上有权值信息时,我们考虑 建立超级源点,将超级源点与
个点分别连一条权值大小为该点上权值的大小的边。
一:
算法(稠密图)
算法总是维护最小生成树的一部分。起初,
算法仅确定1号节点属于最小生成树。
在任意时刻,设已经确定属于最小生成树的节点集合为,剩余节点集合为
。
若,则
表示节点
与集合
中的节点之间权值最小的边的权值。
若,则
就等于
被加入
时选出的最小边的权值。
int prim(){
int res=0;
memset(dist,0x3f,sizeof(dist));
dist[1]=0;
for(int i=0;i<n;i++){
int t=-1;
for(int j=1;j<=n;j++){
if(!vis[j]&&(t==-1||dist[t]>dist[j]))
t=j;
}
res+=dist[t];
vis[t]=1;
for(int j=1;j<=n;j++) dist[j]=min(dist[j],w[t][j]);
}
return res;
}
二:
算法
算法 总是维护无向图的最小生成森林。最初,可认为生成森林由
条边构成,每个节点各自构成一棵仅包含一个点的树。
算法则是将所有边按权值从小到大排序,在任意时刻,
算法从剩余的边中选出一条权值最小的,并且这条边的两端点属于生成森林中两棵不同的树(不连通),把该边加入生成树林。图中节点的联通情况可以用并查集维护。
struct node{
int a,b,w;
}e[M];
int n,m,fa[N],res=0;
int find(int x){
if(fa[x]==x) return fa[x];
return fa[x]=find(fa[x]);
}
bool cmp(node x,node y){
return x.w<y.w;
}
void Kruskal(){
for(int i=1;i<=n;i++) fa[i]=i;
sort(e+1,e+1+m,cmp);
res=0;
for(int i=1;i<=m;i++){
int pa=find(e[i].a),pb=find(e[i].b);
if(pa==pb) continue;
fa[pb]=pa;
res+=e[i].w;
}
}
例题:
基础应用
1. AcWing 1140. 最短网络
最短路模板题,用 算法解决
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,w[N][N],dist[N];
int vis[N];
int prim(){
int res=0;
memset(dist,0x3f,sizeof(dist));
dist[1]=0;
for(int i=0;i<n;i++){
int t=-1;
for(int j=1;j<=n;j++){
if(!vis[j]&&(t==-1||dist[t]>dist[j]))
t=j;
}
res+=dist[t];
vis[t]=1;
for(int j=1;j<=n;j++) dist[j]=min(dist[j],w[t][j]);
}
return res;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&w[i][j]);
printf("%d\n",prim());
return 0;
}
2. AcWing 1141. 局域网
此题求出最小生成树,答案等于 将不属于最小生成树的边 做累加和
#include<bits/stdc++.h>
using namespace std;
const int N=110,M=210;
struct node{
int a,b,w;
}e[M];
int n,m,fa[N];
int find(int x){
if(fa[x]==x) return fa[x];
return fa[x]=find(fa[x]);
}
bool cmp(node x,node y){
return x.w<y.w;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].w);
for(int i=1;i<=n;i++) fa[i]=i;
sort(e+1,e+1+m,cmp);
int res=0;
for(int i=1;i<=m;i++){
int pa=find(e[i].a),pb=find(e[i].b);
if(pa==pb){
res+=e[i].w;
continue;
}
fa[pb]=pa;
}
printf("%d\n",res);
return 0;
}
3. AcWing 1142. 繁忙的都市
求出最小生成树的所有边,树边 的最大值即为答案
#include<bits/stdc++.h>
using namespace std;
const int N=310,M=8010;
struct node{
int a,b,w;
}e[M];
int n,m,fa[N];
int find(int x){
if(fa[x]==x) return fa[x];
return fa[x]=find(fa[x]);
}
bool cmp(node x,node y){
return x.w<y.w;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++)
scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].w);
sort(e+1,e+1+m,cmp);
int ans=0;
for(int i=1;i<=m;i++){
int pa=find(e[i].a),pb=find(e[i].b);
if(pa==pb) continue;
fa[pb]=pa;
ans=max(ans,e[i].w);
}
printf("%d %d\n",n-1,ans);
return 0;
}
4. AcWing 1143. 联络员
本题先将所有的必选边加入到生成森林中,然后将剩下的边从小到大排序,将边的两点 不属于同一颗树的边加到生成森林中,最终生成一颗树。 答案即为该树上边权值的累加和。
#include<bits/stdc++.h>
using namespace std;
const int N=2010,M=10010;
struct node{
int a,b,w;
}e[M];
int n,m,fa[N],cnt;
int find(int x){
if(fa[x]==x) return fa[x];
return fa[x]=find(fa[x]);
}
bool cmp(node x,node y){
return x.w<y.w;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) fa[i]=i;
int ans=0;
for(int i=1;i<=m;i++){
int a,b,w,p;
scanf("%d%d%d%d",&p,&a,&b,&w);
if(p==1){
ans+=w;
int pa=find(a),pb=find(b);
fa[pb]=pa;
}
else e[++cnt]={a,b,w};
}
sort(e+1,e+1+cnt,cmp);
for(int i=1;i<=cnt;i++){
int pa=find(e[i].a),pb=find(e[i].b);
if(pa==pb) continue;
fa[pb]=pa;
ans+=e[i].w;
}
printf("%d\n",ans);
return 0;
}
扩展应用
1. AcWing 1146. 新的开始
使得矿井有电,要么矿井自己建立发电站,要么与其他的的矿井建立电网,因此我们可以建立一个超级源点,该超级源点与每一个矿井连一条权值为 的边,
为在该矿井上建立发电站的费用。
之后我们再在该图上求出最小生成树,此时的最小生成树一共有 个点,
条边(因为加了一个超级源点)。答案即为该最小生成树的边权的累加和。
#include<bits/stdc++.h>
using namespace std;
const int N=310,M=90010;
struct node{
int a,b,w;
}e[M];
int n,cnt,fa[N];
int find(int x){
if(fa[x]==x) return fa[x];
return fa[x]=find(fa[x]);
}
bool cmp(node x,node y){
return x.w<y.w;
}
int main(){
scanf("%d",&n);
for(int i=0;i<=n;i++) fa[i]=i;
for(int i=1;i<=n;i++){
scanf("%d",&e[++cnt].w);
e[cnt].a=0; e[cnt].b=i;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
int w;
scanf("%d",&w);
if(i==j) continue;
e[++cnt].w=w; e[cnt].a=i; e[cnt].b=j;
}
sort(e+1,e+1+cnt,cmp);
int ans=0;
for(int i=1;i<=cnt;i++){
int pa=find(e[i].a),pb=find(e[i].b);
if(pa==pb) continue;
fa[pb]=pa;
ans+=e[i].w;
}
printf("%d\n",ans);
return 0;
}
2. AcWing 1145. 北极通讯网络
我们先将该图生成一颗最小生成树,对于卫星通讯,我们肯定是将其分发给该最小生成树的最长的边的某一个端点,可以使得最长的边最小,因此每减去一条边,就有一个点和该生成树脱离,自我形成一个独立的点,当减去 条边,此时有
个连通块,即每个连通块分发一个卫星通讯,因此答案为 剩余的边 组成的最小生成树的最大的边的权值。
#include<bits/stdc++.h>
using namespace std;
const int N=510,M=N*N;
struct node{
int a,b;
double w;
}e[M],edge[M];
struct dot{
int x,y;
}vil[N];
int n,k,cnt,fa[N],idx,vis[N];
int find(int x){
if(fa[x]==x) return fa[x];
return fa[x]=find(fa[x]);
}
double get_w(dot t1,dot t2){
int dx=t1.x-t2.x,dy=t1.y-t2.y;
return sqrt((double)dx*dx*1.0+(double)dy*dy*1.0);
}
bool cmp(node x,node y){
return x.w<y.w;
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d%d",&vil[i].x,&vil[i].y);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++){
e[++cnt].a=i; e[cnt].b=j;
e[cnt].w=get_w(vil[i],vil[j]);
}
sort(e+1,e+1+cnt,cmp);
for(int i=1;i<=cnt;i++){
int pa=find(e[i].a),pb=find(e[i].b);
if(pa==pb) continue;
fa[pb]=pa;
edge[++idx]=e[i];
}
double ans=0;
// printf("%d\n",idx);
// for(int i=1;i<=idx;i++) printf("%d %d %lf\n",edge[i].a,edge[i].b,edge[i].w);
k--;
for(int i=idx;i>=1;i--){
if(k<=0){
ans=edge[i].w;
break;
}
k--;
}
printf("%.2lf\n",ans);
return 0;
}
3. AcWing 346. 走廊泼水节
题目大意:将一颗树 加边 扩展为一个 完全图,并且不影响在此完全图上求最小生成树仍是之前的那颗原来的树,将加入的边的权值和输出。
y总做题思路:
因此我们将该树的边按权值从小到大排序,若该边两端 属于不同的树,则将
所在的树中的每一个点与
所在的树中的每一个点连一条边,其权值为
,我们将多连的
这条边去掉,因此对答案的贡献为
#include<bits/stdc++.h>
using namespace std;
const int N=6010;
struct node{
int a,b,w;
}e[N];
int T,n,fa[N],siz[N];
int find(int x){
if(fa[x]==x) return fa[x];
return fa[x]=find(fa[x]);
}
bool cmp(node x,node y){
return x.w<y.w;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<n;i++){
int a,b,w;
scanf("%d%d%d",&a,&b,&w);
e[i]={a,b,w};
}
for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
sort(e+1,e+n,cmp);
int ans=0;
for(int i=1;i<n;i++){
int pa=find(e[i].a),pb=find(e[i].b);
if(pa==pb) continue;
ans=ans+(siz[pa]*siz[pb]-1)*(e[i].w+1);
fa[pb]=pa;
siz[pa]+=siz[pb];
}
printf("%d\n",ans);
}
return 0;
}
4. AcWing 1148. 秘密的牛奶运输
严格次小生成树模板题
y总的讲解适用于 非严格次小生成树 ,对于 严格次小生成树,我们应该记录最小生成树上每一对点的路径上的 最大值 和 次大值
,最大值和次大值严格不相等。设
为最小生成树上所有边权的累加和。对于每一条非树边,非树边两端端点分别为
,非树边的权值为
,若
到
的路径上的最大值满足
,则
,若
到
的路径上的次大值满足
,则
代码 1(非 版本)(
版本):
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=510,M=1e4+10;
struct node{
int nex,to,w;
}e[N*2];
struct edges{
int a,b,w;
bool f;
}edge[M];
int n,m,head[N],cnt,fa[N],d1[N][N],d2[N][N];
bool cmp(edges x,edges y){
return x.w<y.w;
}
int find(int x){
if(fa[x]==x) return fa[x];
return fa[x]=find(fa[x]);
}
void add(int u,int v,int w){
e[++cnt].nex=head[u];
e[cnt].to=v;
e[cnt].w=w;
head[u]=cnt;
}
void dfs(int u,int fa,int max1,int max2,int dis1[],int dis2[]){
dis1[u]=max1; dis2[u]=max2;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].to,w=e[i].w;
if(v==fa) continue;
int td1=max1,td2=max2;
if(w>td1) td2=td1,td1=w;
else if(w>td2&&w<td1) td2=w;
dfs(v,u,td1,td2,dis1,dis2);
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++){
int a,b,w;
scanf("%d%d%d",&a,&b,&w);
edge[i]={a,b,w};
}
sort(edge+1,edge+1+m,cmp);
ll ans=0;
for(int i=1;i<=m;i++){
int a=edge[i].a,b=edge[i].b,w=edge[i].w;
int pa=find(a),pb=find(b);
if(pa==pb) continue;
fa[pb]=pa;
edge[i].f=true;
ans+=w;
add(a,b,w); add(b,a,w);
}
for(int i=1;i<=n;i++) dfs(i,0,0,0,d1[i],d2[i]);
ll res=1e18;
for(int i=1;i<=m;i++){
if(edge[i].f) continue;
int a=edge[i].a,b=edge[i].b,w=edge[i].w;
if(w>d1[a][b]) res=min(res,ans-d1[a][b]+w);
else if(w>d2[a][b]&&d2[a][b]) res=min(res,ans-d2[a][b]+w);
}
printf("%lld\n",res);
return 0;
}