图论-最小生成树专题

最小生成树

Prim板子

先给一个我用的板子

  1. Prim算法 图的点集合为V 是从点出发 任意定一个初始节点加入集合X,寻找它所连接的最短边,然后将它连接的节点加入X中,用新连接的点更新d数组,直至X=V
  2. d数组存的是当前X集合对各个点的最短距离 vis记录这个点是否已经在X集合中
  3. 类似Dijkstra–每次找到最小的边
//无向图 N点 M条边 求最小生成树
//可能存在重边

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdio>

using namespace std;

const int N = 5100;
int g[N][N];//图的邻接矩阵
bool vis[N];//标记各个点是否已经进入集合X
int d[N];//X集合 到各个点的最短距离
int n,m;

int prim()
{
    int sum=0;
    memset(vis,0,sizeof(vis));
    memset(d,0x3f,sizeof(d));
    d[1]=0;
    while(1)
    {
        int t=-1;
        for(int i=1;i<=n;i++)
            if(!vis[i]&&(t==-1||d[i]<d[t]))t=i;

        if(t==-1)break;
        if(d[t]==0x3f3f3f3f)return -1;
        vis[t]=true;
        sum+=d[t];
        //cout<<t<<"+++"<<d[t]<<"+++"<<sum<<endl;
        for(int i=1;i<=n;i++)
            d[i]=min(d[i],g[t][i]);

    }
    for(int i=1;i<=n;i++)
        if(!vis[i])return -1;
    return sum;
}
int main()
{

    scanf("%d%d",&n,&m);
    memset(g,0x3f,sizeof(g));
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        g[a][b]=min(g[a][b],c);
        g[b][a]=min(g[b][a],c);
    }
    int t=prim();
    if(t==-1||n==1)printf("orz\n");
    else printf("%d\n",t);

    return 0;
}

Prim的例题

P2872 [USACO07DEC]Building Roads S

题目链接:P2872 [USACO07DEC]Building Roads S

题意:n个点 m条边
求加入一些边构成最小生成树
分析:Prim板子题 稍微改一下板子,既然已经有相连,那就把距离改成0就可以了,初始点设任意一个点都可以
知识点:Prim

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<cmath>

using namespace std;

const int N = 1100;
const double inf = 0x3f;
double g[N][N],d[N];
bool vis[N];
int n,m;
long long x[N],y[N];

double ju(int i,int j)
{
    double xx=x[i]-x[j];
    double yy=y[i]-y[j];
    //cout<<xx<<' '<<yy<<endl;
    return sqrt(xx*xx+yy*yy);
}

double prim()
{
    double sum=0;
    d[1]=0;
    while(true)
    {
        int t=-1;
        for(int i=1;i<=n;i++)
        if(!vis[i]&&(t==-1||d[t]>d[i]))t=i;

        if(t==-1)break;
        sum+=d[t];
        vis[t]=true;
        //cout<<'+'<<sum<<endl;
        for(int i=1;i<=n;i++)
        d[i]=min(d[i],g[t][i]);
    }
    return sum;
}

int main()
{
    scanf("%d%d",&n,&m);
    memset(g,0x7f,sizeof(g));
    //cout<<g[1][1]<<endl;
    for(int i=1;i<=n;i++)
        scanf("%lld%lld",&x[i],&y[i]);

    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
            g[i][j]=min(g[i][j],ju(i,j));//cout<<g[i][j]<<' ';
            //cout<<endl;;
    }
    memset(d,0x7f,sizeof(d));
    while(m--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        g[a][b]=0;
        g[b][a]=0;
    }
    printf("%.2f\n",prim());

    return 0;
}

P1194 买礼物

题目链接:P1194 买礼物

题意:买B件物品,每两个物品一起买有优惠,一个物品单独买价格为A,翻译一下,就是说每两个物品有一条边,权值为优惠价格,物品有一个自环是A,找最小生成树,画最少的钱买到所有物品

分析:建好图,跑一遍Prim
ps:初始点是A 第一个物品没有优惠 原价购买

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>

using namespace std;

const int N = 510;
int g[N][N];
bool vis[N];
int d[N];
int A,B;

int prim()
{
    memset(d,0x3f,sizeof(d));
    d[1]=A;
    int sum=0;
    while(1)
    {
        int t=-1;
        for(int i=1;i<=B;i++)
            if(!vis[i]&&(t==-1||d[t]>d[i]))t=i;

        if(t==-1)break;
        vis[t]=true;
        sum+=d[t];
        //cout<<sum<<endl;
        for(int i=1;i<=B;i++)
            d[i]=min(d[i],min(g[t][i],g[i][i]));//存在自环
    }
    return sum;
}

int main()
{
    scanf("%d%d",&A,&B);
    for(int i=1;i<=B;i++)
    {
        for(int j=1;j<=B;j++)
        {
            int x;
            scanf("%d",&x);
            if(x==0)g[i][j]=A;
            else g[i][j]=x;
            //cout<<g[i][j]<<"  ";
        }
       // cout<<endl;
    }

    cout<<prim()<<endl;

    return 0;
}
/*
3 3
0 4 4
4 0 4
4 4 0

9
*/

P1265 公路修建

题目链接:P1265 公路修建

题意:跟上面P2872类似,这次没有相连的点,所有点开始都是一个城市,将他们联通到一个连通体,给各个城市的坐标,计算最小距离
分析:就是一个最小生成树的题,计算联通各个点的最小距离(不存各个点到其它点的距离了,直接到更新计算)

~~ps:Kruskal跑了90分 边太多了 5000*5000 MLE了 稠密图还是用Prim吧
~~
知识点:最小生成树 Prim

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>

using namespace std;

const int N = 5001;
double d[N];
bool vis[N];
int x[N],y[N];
int n;

double Dis(int i,int j)
{
    double xx=x[i]-x[j];
    double yy=y[i]-y[j];
    return sqrt(xx*xx+yy*yy);
}

double prim()
{
    memset(d,0x7f,sizeof(d));
    d[1]=0;
    double sum=0;
    while(1)
    {
        int t=-1;
        for(int i=1;i<=n;i++)
            if(!vis[i]&&(t==-1||d[t]>d[i]))t=i;
        if(t==-1)break;
        sum+=d[t];
        vis[t]=true;

        for(int i=1;i<=n;i++)
            d[i]=min(d[i],Dis(i,t));

    }
    return sum;
}

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>x[i]>>y[i];

    printf("%.2f\n",prim());

    return 0;
}

P1661 扩散

题目链接:P1661 扩散

题意:n个点,每个点可以在一个单位时间向四周扩散一个单位,问将所有点相连到一起的连通体需要经过的最短时间

分析:用最小生成树来做,那最短时间就是这些边里最长的,每两个点连起来,最后构成时间最小的生成树,不重复不成环
看题解好像也可以使用二分来做,大家可以去试试

知识点:Prim

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>

using namespace std;

const int N = 100;

bool vis[N];
int d[N];
int n;
int x[N],y[N];

int Dis(int i,int j)
{
    int xx=fabs(x[i]-x[j]);
    int yy=fabs(y[i]-y[j]);
    if((xx+yy)%2==0)return (xx+yy)/2;
    return  (xx+yy)/2+1;
}

int Prim()
{
    memset(d,0x3f,sizeof(d));
    d[1]=0;
    int ans=0;
    while(1)
    {
        int t=-1;
        for(int i=1;i<=n;i++)
            if(!vis[i]&&(t==-1||d[t]>d[i]))t=i;
        if(t==-1)break;
        vis[t]=true;
        ans=max(ans,d[t]);
        //cout<<d[t]<<endl;
        //cout<<d[t]<<endl;
        for(int i=1;i<=n;i++)
        d[i]=min(d[i],Dis(i,t));
    }
    return ans;
}

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>x[i]>>y[i];
    cout<<Prim()<<endl;
    return 0;
}
/*
5
46 16
6 5
33 18
5 17
7 25
*/

Kruskal

Kruskal板子

  1. 将所有边按权重从小到大排序
  2. 将最小权重的的边,取出来 a-b 权值为c
    if(a 和 b 不连通)把这条边加入集合里
  3. 直到集合里的点==n
#include<iostream>
#include<cstdio>
#include<algorithm>

using namespace std;

const int N = 1e5+10,M = 2e5+10;

struct Node
{
    int a,b,c;
}node[M];//存储边

int p[N];//并查集 各个节点的树根节点
int n,m;//点数 边数

bool cmp(const struct Node& x,const struct Node& y)
{
    return x.c<y.c;
}

int find(int x)//查找x的树根节点
{
    if(p[x]!=x)p[x]=find(p[x]);
    return p[x];
}

int kruskal()//最小生成树
{
    sort(node+1,node+1+m,cmp);//将边从小到大排序
    for(int i=1;i<=n;i++)//初始化并查集 父亲开始均为自己
        p[i]=i;
    
    int res=0,cnt=0;//res-目前的代价 cnt-走过的边数
    
    for(int i=1;i<=m;i++)
    {
        Node e=node[i];
        if(find(e.a)==find(e.b))continue;//已经加入到集合里了 那就不需要再加这条边
        p[find(e.a)]=find(e.b);
        res+=e.c;
        cnt++;
    }
    if(cnt<n-1)return -1;//边数<n-1 图不连通
    return res;
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&node[i].a,&node[i].b,&node[i].c);
    }
    
    int t=kruskal();
    
    if(t==-1)puts("impossible");
    else printf("%d\n",t);
    
    return 0;
}

Kruskal 例题

P1991 无线通讯网

P1991 无线通讯网

题意:n个点s个卫星电话,将n个点通信,有直接和间接通信方式,直接相连,和两点都安装卫星电话也可以通信

分析:完全图,n个点,将各个点想连,按照距离排序,想让无线电收发器的传输距离最小,按照Kruskal算法,只需要将结束条件改为p-s后面距离大的带你都按转卫星电话,使传输距离最小

知识点:最小生成树

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>

using namespace std;

const int N = 500001;
struct Node
{
    int a,b;
    double c;
    bool operator<(const Node & X)
    {
        return c<X.c;
    }
}w[N];
int s,p,cnt=0;
int x[510],y[510];
int f[510];

double Dis(int i,int j)
{
    double xx=x[i]-x[j];
    double yy=y[i]-y[j];
    return sqrt(xx*xx+yy*yy);
}

int findd(int x)
{
    if(f[x]!=x)f[x]=findd(f[x]);
    return f[x];
}

double kurskal()
{
    sort(w,w+cnt);
    for(int i=1;i<=p;i++)f[i]=i;
    double mi;
    int shu=0;
    for(int i=0;i<cnt;i++)
    {
        Node e=w[i];
        //cout<<e.c<<endl;
        if(findd(e.a)==findd(e.b))continue;
        f[findd(e.a)]=findd(e.b);
        mi=max(e.c,mi);
        shu++;
        if(shu>=p-s)
        {
            return mi;
        }
    }
}

int main()
{
    cin>>s>>p;
    for(int i=1;i<=p;i++)
        cin>>x[i]>>y[i];
    for(int i=1;i<=p;i++)
        for(int j=i+1;j<=p;j++)
            w[cnt++]={i,j,Dis(i,j)};

    double t=kurskal();
    printf("%.2f\n",t);
    return 0;
}

P2121 拆地毯

P2121 拆地毯

题意:有n个块区域,m个地毯将他们相连,最后需要k条地毯的最大美丽度
这k条,不能构成环
分析:最小生成树变形,求最大,将权值从小到大排序

知识点:并查集+Kruskal

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 1e5+10;
struct Node
{
    int a,b,c;
    bool operator < (const Node& X)
    {
        return c>X.c;
    }
}w[N];
int n,m,k;
int p[N];

int fin(int x)
{
    if(p[x]!=x)p[x]=fin(p[x]);
    return p[x];
}

int Kruskal()
{
    for(int i=1;i<=n;i++)p[i]=i;
    sort(w,w+m);
    int sum=0,shu=0;
    for(int i=0;i<m;i++)
    {
        Node e=w[i];
        if(fin(e.a)==fin(e.b))continue;
        p[fin(e.a)]=fin(e.b);
        shu++;
        sum+=e.c;
        if(shu>=k)return sum;
    }

}

int main()
{
    cin>>n>>m>>k;
    for(int i=0;i<m;i++)
        cin>>w[i].a>>w[i].b>>w[i].c;

    int t=Kruskal();
    printf("%d\n",t);

    return 0;
}

P4047 [JSOI2010]部落划分

P4047 [JSOI2010]部落划分

题意:n个野人居住的地点,要划分为k个部落,使靠近的两个点距离最大化,输出剩下最大距离里的最小距离

分析:就是说在一个完全图里,分为k个连通体,让每个连通图相互距离最大,其实就是一个最小生成树,将n-k的节点距离相对小的划分在一起,对其它节点来说就是最大,使用Kruskal算法将终止条件改为达到n-k就停止,然后输出下一个该划入集合的距离就是答案

知识点:并查集+Kruskal

#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>

using namespace std;

const int N = 1e6+10;
struct Node
{
    int a,b;
    double c;
    bool operator < (const Node& X)
    {
        return c<X.c;
    }
}w[N];

int x[N],y[N];
int p[N];
int n,m,cnt=0;

double Dis(int i,int j)
{
    double xx=x[i]-x[j];
    double yy=y[i]-y[j];
    return sqrt(xx*xx+yy*yy);
}

int fin(int x)
{
    if(p[x]!=x)p[x]=fin(p[x]);
    return p[x];
}

void Kruskal()
{
    sort(w,w+cnt);
    for(int i=1;i<=n;i++)p[i]=i;
    int shu=0;
    for(int i=0;i<cnt;i++)
    {
        Node e=w[i];
        if(fin(e.a)==fin(e.b))continue;
        p[fin(e.a)]=fin(e.b);
        shu++;
        if(shu>n-m)
        {
            printf("%.2f\n",e.c);
            break;
        }
    }

}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>x[i]>>y[i];

    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
            w[cnt++]={i,j,Dis(i,j)};

    Kruskal();

    return 0;
}

P1195 口袋的天空

题目链接:P1195 口袋的天空

题意:大概就是说,现在窗外有n朵云,有m条边,你要将他们连成k个连通体,求最小代价

分析:板子,最小生成树,也就是说将连成k个连通体,肯定把短的连,这样才能代价最小,所以就连前面n-k的边就可以
看代码,Kruskal 当加入n-k边之后停止 若达不到n-k则构不成k个连通体

知识点:Kruskal+并查集

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>

using namespace std;

const int N = 1e4+10;
struct Node
{
    int a,b,c;
    bool operator < (const Node & X)
    {
        return c<X.c;
    }
}w[N];
int n,m,k;
int p[1001];

int fin(int x)
{
    if(p[x]!=x)p[x]=fin(p[x]);
    return p[x];
}

int Kruskal()
{
    for(int i=1;i<=n;i++)p[i]=i;
    int cnt=0,sum=0;
    sort(w+1,w+1+m);

    for(int i=1;i<=m;i++)
    {
        Node e=w[i];
        if(fin(e.a)==fin(e.b))continue;
        p[fin(e.a)]=fin(e.b);
        cnt++;
        sum+=e.c;
        if(cnt>=n-k)
        {
            return sum;
        }
    }
    return -1;
}

int main()
{
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++)
        cin>>w[i].a>>w[i].b>>w[i].c;

    int t=Kruskal();
    if(t==-1)puts("No Answer");
    else printf("%d\n",t);
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值