最小生成树

用prim和kruskal算法求最小生成树问题(基础)

kruskal是常用的

prim有朴素版本和 堆优化版本(基本不用) 用邻接矩阵

如果是稀疏图就用kruskal   O( mlogn ) 直接用三元组存边

kruskal:基于并查集

1.先把所有边排序

2.然后从小到大枚举所有边

3.如果说当前这条边的两个点连通了 那么就不操作 如果当前的两个点所在的连通块不连通 那么就加到最小生成树上

prim步骤:

原理:从某一个点开始逐渐把所有点和这个点连通 每一次连通选着当前这个点所在连通块和外面连的所有边 从当前所有和外界里面选一条最短的边就加到连通块去

两个算法的证明 

1. 最短网络

1140. 最短网络 - AcWing题库


 

裸题

代码

#include <bits/stdc++.h>
using namespace std;
const int N=110;
int n;
int w[N][N];
int dis[N];
bool st[N];
int prim()
{
    int res=0;
    memset(dis,0x3f,sizeof dis);
    dis[1]=0;
    for(int i=0;i<n;i++)
    {
        int t=-1;
        for(int j=1;j<=n;j++)
        {
            if(!st[j]&&(t==-1||dis[t]>dis[j]))
                t=j;
        }
        res+=dis[t];
        st[t]=true;
        for(int j=1;j<=n;j++) dis[j]=min(dis[j],w[t][j]);
    }
    return res;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
            cin>>w[i][j];
    }
    cout<<prim()<<endl;
    return 0;
}

 2.局域网

信息学奥赛一本通(C++版)在线评测系统

这题是早这个图的每个连通块内(一开始所有计算机不是连通的,是有一定的连通块),求一颗生成树,相当于求“生成森林”   那么这题就是求每一个连通块内的最小生成树

做kruskal算法

1.将所有边从小到大排序

2.依次枚举每条边的a b w 如果a b 不连通,那么就将当前边加到最小生成树去

#include <bits/stdc++.h>
using namespace std;
const int N=110,M=220;
struct node
{
    int a,b,w;
    bool operator< (const node &t)const
    {
        return w<t.w;
    }
}e[M];
int p[N];
int n,m;
int find(int x)
{
    if(p[x]!=x) return p[x]=find(p[x]);
    return p[x];
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) p[i]=i;
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        e[i]={a,b,c};
    }
    sort(e,e+m);
    int res=0;
    for(int i=0;i<m;i++)
    {
        int a=find(e[i].a),b=find(e[i].b);
        if(a!=b) p[a]=b;
        else res+=e[i].w;
    }
    cout<<res<<endl;
    return 0;
}

3.繁忙的都市

信息学奥赛一本通(C++版)在线评测系统

本题的最小生成树:最大的边权最小

做法

1.将所有边从小到大排序

2.从小到大枚举每条边 a,b,w

如果已经连通 pass

如果没连通 那么就将当前边选出来

就是kruskal

#include <bits/stdc++.h>
using namespace std;
const int N=310,M=10010;
struct node
{
    int a,b,w;
    bool operator< (const node &t) const
    {
        return w<t.w;
    }
}e[M];
int p[N];
int n,m;
int find(int x)
{
    if(p[x]!=x) return p[x]=find(p[x]);
    return p[x];
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) p[i]=i;
    for(int i=0;i<m;i++)
    {
        int a,b,w;
        cin>>a>>b>>w;
        e[i]={a,b,w};
    }
    sort(e,e+m);
    int res=0;
    for(int i=0;i<m;i++)
    {
        int a=find(e[i].a),b=find(e[i].b);
        int w=e[i].w;
        if(a!=b)
        {
            p[a]=b;
            res=w;
        }
    }
    cout<<n-1<<" "<<res<<endl;
    return 0;
}

4.联络员

先把必选的全选上 再做kruskal

#include <bits/stdc++.h>
using namespace std;
const int N=2010,M=10010;
int n,m;
struct node
{
    int a,b,w;
    bool operator< (const node &t)const
    {
        return w<t.w;
    }
}e[M];
int p[N];
int find(int x)
{
    if(p[x]!=x) return p[x]=find(p[x]);
    return p[x];
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        p[i]=i;
    int res=0,k=0;
    for(int i=0;i<m;i++)
    {
        int t,a,b,w;
        cin>>t>>a>>b>>w;
        if(t==1)
        {
            res+=w;
            p[find(a)]=find(b);
        }
        else e[k++]={a,b,w};
    }
    sort(e,e+k);
    for(int i=0;i<k;i++)
    {
        int a=find(e[i].a),b=find(e[i].b),w=e[i].w;
        if(a!=b)
        {
            p[a]=b;
            res+=w;
        }
    }
    cout<<res<<endl;
    return 0;
}

5.连接格点

和上题思路一样

kruskal要排序 但是这题边权只有1 2所以我们先把边权为1的全建出来 再把边权为2的点建出来 所以就省了排序

#include<bits/stdc++.h>
using namespace std;
const int N=1010,M=N*N,K=2*M;
int ids[N][N];//用来将坐标映射到点
int n,m,k;
struct Edge
{
    int a,b,w;
}e[K];
int p[M];
int find(int x)//并查集
{
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
void edge()
{
    int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1},dw[4]={1,2,1,2};//四个方向
    for(int s=0;s<2;s++)//s表示余数,0表示打横走,1表示纵着走
        for(int i=1;i<=n;i++)
          for(int j=1;j<=m;j++)
             for(int u=0;u<4;u++)
                if(u%2==s)//走的途径
               {
                    int x=i+dx[u],y=j+dy[u],w=dw[u];
                    if(x<=0||x>n||y<=0||y>m) continue;//假如越界
                    int a=ids[i][j],b=ids[x][y];//获取当前位置
                    if(a<b) e[k++]={a,b,w};//为了避免重复假如
               }
}
int main()
{
  cin>>n>>m;
  for(int i=1;i<=n*m;i++) p[i]=i;//初始化
  for(int i=1,t=1;i<=n;i++)//映射坐标为点
    for(int j=1;j<=m;j++,t++)
        ids[i][j]=t;
  int x1,x2,y1,y2;
  while(cin>>x1>>y1>>x2>>y2)
  {
      int a=ids[x1][y1],b=ids[x2][y2];
      p[find(a)]=find(b);//假如是连通块了,则加到同个连通块中
  }
  edge();//处理每个走的方式
  int res=0;
  for(int i=0;i<k;i++)
  {
      int a=find(e[i].a),b=find(e[i].b),w=e[i].w;
      if(a!=b)//假如不在一个连通块
      {
          res+=w;//则加上边权
          p[a]=b;//加到同一个连通块中
      }
  }
  cout<<res<<endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值