洛谷题单——【图论2-3】最小生成树

P3366 【模板】最小生成树

一道模板题,prim算法就可以过,krustal算法也是可以的嗷
prim算法

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

krustal算法

#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, m;
int p[N]; //用并查集,维护边的权重,降低时间复杂度
struct Edge
{
    int a, b, w;
    bool operator<(const Edge &W) const
    {
        return w < W.w;
    }
} edges[N];
int find(int x) //并查集中找祖宗节点+优化的函数
{
    if (p[x] != x)
        p[x] = find(p[x]);
    return p[x];
}
int main()
{
    cin >> n >> m;
    for (int i = 0; i < m; i++)
    {
        int a, b, w;
        cin >> a >> b >> w;
        edges[i] = {a, b, w};
    }
    sort(edges, edges + m);
    for (int i = 1; i <= n; i++) //初始化并查集
        p[i] = i;
    int res = 0; //存储最小生成树中所有边的权重之和
    int cnt = 0; //存储当前一共加了多少条边
    for (int i = 0; i < m; i++)
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;
        a = find(a), b = find(b); //并查集中的算法
        if (a != b)
        {
            p[a] = b;
            res += w;
            cnt++;
        }
    }
    if (cnt < n - 1) //表示存在不连通的点,即不存在最小生成树
        cout << "impossible" << endl;
    else //输出答案
        cout << res << endl;
    return 0;
}

口袋的天空

krustal算法的应用
要用n个节点连接出k个连通块,就一定需要n-k条边
因此只需要在krustal算法枚举了n-k条边之后跳出循环即可找到答案
如果m<n-k则一定无法找到k个连通块,也就没有答案

#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, m, k;
int p[N]; //用并查集,维护边的权重,降低时间复杂度
struct Edge
{
    int a, b, w;
    bool operator<(const Edge &W) const
    {
        return w < W.w;
    }
} edges[N];
int find(int x) //并查集中找祖宗节点+优化的函数
{
    if (p[x] != x)
        p[x] = find(p[x]);
    return p[x];
}
int main()
{
    cin >> n >> m >> k;
    for (int i = 0; i < m; i++)
    {
        int a, b, w;
        cin >> a >> b >> w;
        edges[i] = {a, b, w};
    }
    sort(edges, edges + m);
    for (int i = 1; i <= n; i++) //初始化并查集
        p[i] = i;
    int res = 0; //存储最小生成树中所有边的权重之和
    int cnt = 0; //存储当前一共加了多少条边
    for (int i = 0; i < m; i++)
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;
        a = find(a), b = find(b); //并查集中的算法
        if (a != b)
        {
            p[a] = b;
            res += w;
            cnt++;
        }
        if(cnt==n-k)
        {
            cout<<res<<endl;
            return 0;
        }
    }
    cout<<"No Answer"<<endl;
    return 0;
}

拆地毯

超水一道题嗷,只需要把krustal算法模板中排序的顺序变一下就可

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
struct Edge
{
    int a, b;
    int w;
} edges[N];
int n, m, k;
int p[N];
int cmp(const Edge &a, const Edge &b)
{
    return a.w > b.w;
}
int find(int x)
{
    if (p[x] != x)
        p[x] = find(p[x]);
    return p[x];
}
int main()
{
    cin >> n >> m >> k;
    for (int i = 0; i < m; i++)
        cin >> edges[i].a >> edges[i].b >> edges[i].w;
    for (int i = 1; i <= n; i++)
        p[i] = i;
    sort(edges, edges + m, cmp);
    int res = 0;
    int cnt = 0;
    for (int i = 0; i < m; i++)
    {
        int a = find(edges[i].a), b = find(edges[i].b);
        if(a!=b)
        {
            p[a]=b;
            res=res+edges[i].w;
            cnt++;
        }
        if(cnt==k) break;
    }
    cout<<res<<endl;
    return 0;
}

[USACO07DEC]Building Roads S

这道题用krustal算法思路还是比较清淅的

1、首先读入所有的点的坐标,存在p数组中
2、初始化所有可能存在的边的信息存在edges数组中
3、读入所有已经存在的边的,并将这些边的距离默认为0,存在edges数组中
4、对结构体按边权升序排列
5、进行krustal算法,当加入n-1条边的时候跳出循环,此时即可得到边权的最小值

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+10; //注意这里的数据范围
int n, m;
struct point
{
    double x, y;
} p[N];
struct Edge
{
    int a, b;
    double w;
} edges[N];
int cnt=1;
int b[N];
int find(int x)
{
    if (b[x] != x)
        b[x] = find(b[x]);
    return b[x];
}
double length(int i, int j)
{
    double res;
    res = sqrt((p[i].x - p[j].x) * (p[i].x - p[j].x) + (p[i].y - p[j].y) * (p[i].y - p[j].y));
    return res;
}
int cmp(const Edge &a, const Edge &b)
{
    return a.w < b.w;
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> p[i].x >> p[i].y;
    for (int i = 1; i <= n; i++)
    {
        for (int j = i + 1; j <= n; j++)
        {
            double w = length(i, j);
            edges[cnt++] = {i, j, w};
        }
    }
    for (int i = 1; i <= n; i++)
        b[i] = i;
    for (int i = 0; i < m; i++)
    {
        int a, b;
        cin >> a >> b;
        edges[cnt++] = {a, b, 0.0};
    }
    sort(edges + 1, edges + cnt, cmp);
    int num=0;
    double res=0;
    for(int i=1;i<cnt;i++)
    {
        int x=find(edges[i].a),y=find(edges[i].b);
        if(x!=y)
        {
            b[x]=y;
            num++;
            res=res+edges[i].w;
        }
        if(num==n-1) break;
    }
    printf("%.2lf\n",res);
    return 0;
}

买礼物

这道题输入的矩阵不能直接用作邻接矩阵

因为 没有优惠的点之间优惠方案的花费大于单独购买时 需要进行特殊处理
没有优惠的点之间:默认花费为单独购买的价格
优惠方案的花费大于单独购买时:优惠方案就不应该被选择,因此初始化成单独购买的价格

之后我们就只需要用最小生成树的算法来求解即可了
这里给出的题解是循环b次的pirm算法,只需要购买b个礼物因此循环b次即可

#include <bits/stdc++.h>
using namespace std;
const int N = 510;
typedef long long ll;
int a, b;
int g[N][N];
int dist[N];
bool st[N];
ll pirm()
{
    memset(dist,0x3f,sizeof(dist));
    ll res = 0;
    for (int i = 0; i < b; i++)
    {
        int t = -1;
        for (int j = 1; j <= b; j++) //找到未在集合中的点距离当前集合最近的点
        {
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        }
        if (i) //如果不是第一次的话,将距离叠加,只有一个点没有最小生成树的概念
            res += dist[t];
        for (int j = 1; j <= b; j++) //用这个点的距离更新这个点到集合的距离
            dist[j] = min(dist[j], g[t][j]);
        st[t] = true;
    }
    return res+a;
}
int main()
{
    cin >> a >> b;
    for (int i = 1; i <= b; i++)
    {
        for (int j = 1; j <= b; j++)
        {
            int w;
            cin>>w;
            if(w==0||w>a) g[i][j]=a;
            else g[i][j]=w;
        }
    }
    ll res=pirm();
    cout<<res<<endl;
    return 0;
}

营救

一看这道题的题目,求最大值最小,推测应该使用二分法
但是细想这道题根本没必要用二分法
因为在krustal算法中有对边权进行排序的操作,如果我们按照升序排序,那么后搜索到的边权一定大,所以在这里不在需要使用二分法
只需要注意在这里当s和t同属一个集合的时候应该跳出循环,此时搜索到的这条边的权重就是所有边权最大值最小的结果

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,s,t;
struct Edge
{
    int a,b;
    int w;
}edges[N];
int p[N];
int cmp(const Edge &a,const Edge &b)
{
    return a.w<b.w;
}
int find(int x)
{
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
int main()
{
    cin>>n>>m>>s>>t;
    for(int i=0;i<m;i++)
    cin>>edges[i].a>>edges[i].b>>edges[i].w;
    sort(edges,edges+m,cmp);
    for(int i=0;i<n;i++) p[i]=i;
    int res=0;
    for(int i=0;i<m;i++)
    {
        int a=find(edges[i].a),b=find(edges[i].b);
        if(a!=b)
        {
            p[a]=b;
            res=edges[i].w;
        }
        if(find(s)==find(t)) break;
    }
    cout<<res<<endl;
    return 0;
}

无线通讯网

这道题和krustal算法模板中唯一的区别就是退出循环的条件
这里只需要建立s-p条边即可,剩下的站点之间可以通过卫星电话来进行通话

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6;
int s, p;
double x[N], y[N];
int cnt=0;
struct Edge
{
    int a,b;
    double w;
}edges[N];
int b[N];
double length(int i, int j)
{
    double res;
    res = sqrt((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]));
    return res;
}
int find(int x)
{
    if(b[x]!=x) b[x]=find(b[x]);
    return b[x];
}
int cmp(const Edge &a, const Edge &b)
{
    return a.w < b.w;
}
int main()
{
    cin >> s >> p;
    for (int i = 0; i < p; i++)
        cin >> x[i] >> y[i];
    for (int i = 0; i < p; i++)
    {
        for (int j = i + 1; j < p; j++)
        {
            double w = length(i, j);
            edges[cnt++] = {i, j, w};
        }
    }
    sort(edges,edges+cnt,cmp);
    for(int i=0;i<=p;i++) b[i]=i;
    int num=0;
    double res=0;
    for(int i=0;i<cnt;i++)
    {
        int x=find(edges[i].a),y=find(edges[i].b);
        if(x!=y)
        {
            b[x]=y;
            num++;
            res=edges[i].w;
        }
        if(num==p-s) break;
    }
    printf("%.2lf\n",res);
    return 0;
}

[JSOI2010]部落划分

整体思路与上一题类似嗷
初始将每一个点看作是一个部落,每在部落之间连一条边即可表示部落数量-1,当部落数量是我们要划分的数量时,下一条在部落之间连的线就是部落之间距离的最小值

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6;
int n, k;
double x[N], y[N];
int cnt = 0;
struct Edge
{
    int a, b;
    double w;
} edges[N];
int b[N];
double length(int i, int j)
{
    double res;
    res = sqrt((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]));
    return res;
}
int find(int x)
{
    if (b[x] != x)
        b[x] = find(b[x]);
    return b[x];
}
int cmp(const Edge &a, const Edge &b)
{
    return a.w < b.w;
}
int main()
{
    cin >> n >> k;
    for (int i = 0; i < n; i++)
        cin >> x[i] >> y[i];
    for (int i = 0; i < n; i++)
    {
        for (int j = i + 1; j < n; j++)
        {
            double w = length(i, j);
            edges[cnt++] = {i, j, w};
        }
    }
    sort(edges, edges + cnt, cmp);
    for (int i = 0; i <= n; i++)
        b[i] = i;
    int num = n;
    for (int i = 0; i < cnt; i++)
    {
        int x = find(edges[i].a), y = find(edges[i].b);
        if (x != y)
        {
            b[x] = y;
            num--;
        }
        if (num == k)
        {
            for (int j = i + 1; j < cnt; j++)
            {
                int x0 = find(edges[j].a), y0 = find(edges[j].b);
                if (x0 != y0)
                {
                    printf("%.2lf\n", edges[j].w);
                    break;
                }
            }
            break;
        }
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值