prim算法实例(最少花费建发电站)

算法(prim)

prim算法只与点的数量有关O(n^2)

(最小生成树) O(n2)
将题目中所有发电站和电线看成一张无向图,搭建电线看成是将图上两个点连接起来。

将发电站看成最远点(连通块始点)

根据题意,最终得到的图是若干个连通块,每个连通块中有一个点建立发电站。

考虑新建一个 0点,向 1∼n 中所有点 i 连一条长度是 Ci 的边。

那么在某个点 i 建立发电站就可以看成选择 0→i的这条边。

那么我们要做的,就转化为:给定一张无向图,选若干条边,使得图连通。

而这恰是最小生成树。因此我们只需建图并求最小生成树即可。

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

using namespace std;

typedef long long ll;
const int N = 2005;

int n, m, p[N];
int x[N], y[N], k[N];
struct Edge {
    int a, b; ll c;
    inline bool operator < (const Edge& t) const {
        return c < t.c;
    }
} edge[N * N];
int cnt;
bool st[N * N]; // 用于记录每条边是否被选

int find(int x) {
    return x != p[x] ? p[x] = find(p[x]) : x;
}

// 求最小生成树并返回最小生成树的权值和
ll kruskal() {
    ll res = 0;
    sort(edge, edge + cnt);
    for (int i = 0; i < cnt; ++i) {
        int a = find(edge[i].a), b = find(edge[i].b);
        if (a != b) {
            p[a] = b;
            res += edge[i].c;
            st[i] = true;
        }
    }
    return res;
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d%d", x + i, y + i);
        p[i] = i;
    }
    for (int i = 1; i <= n; ++i) {
        int c;
        scanf("%d", &c);
        edge[cnt++] = {i, 0, c}; // 加入一条从 0 连向 i 的边
    }
    for (int i = 1; i <= n; ++i) {
        scanf("%d", k + i);
        for (int j = 1; j < i; ++j) // 建图,将所有的边 (i, j) 连起来,边权为电线每单位花费乘长度
            edge[cnt++] = {i, j, (ll)(k[i] + k[j]) * (abs(x[i] - x[j]) + abs(y[i] - y[j]))};
    }
    printf("%lld\n", kruskal());
    vector<int> v;             // 存建立发电站的点
    vector<pair<int, int>> es; // 存连电线的点对
    for (int i = 0; i < cnt; ++i)
        if (st[i])
            if (!edge[i].b) v.push_back(edge[i].a); // edge[i].b 是 0 说明选择了从 0 连向这个点的边,也就是建立了发电站
            else es.push_back({edge[i].a, edge[i].b});
    printf("%d\n", v.size());
    for (int& x : v) printf("%d ", x);
    printf("\n%d\n", es.size());
    for (auto& [x, y] : es) printf("%d %d\n", x, y);
    return 0;
}

 


这里是 kruskal 算法求最小生成树,时间复杂度为 O(n2logn)

更优的算法是用 prim 算法,时间复杂度为 O(n2),但用 prim 算法不太好求具体方案,故这里选择 kruskal。

转自:

作者:垫底抽风
链接:https://www.acwing.com/solution/content/54472/
来源:AcWing

 


prim算法:

#include <bits/stdc++.h>

#define x first
#define y second

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 2010;

int n;
PII q[N];
int wc[N],wk[N];
int dist[N],distp[N];
bool st[N];
vector<int> ans1;
vector<PII> ans2;
inline LL get_dist(int a,int b){
    int dx=q[a].x-q[b].x;
    int dy=q[a].y-q[b].y;
    return (LL)(abs(dx)+abs(dy))*(wk[a]+wk[b]);
}
LL prim()
{
    LL res=0;
    dist[0]=0;
    st[0]=1;
    for (int i = 1; i <= n; i ++ )dist[i]=wc[i];
    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;
            }
        }
        st[t]=1;
        res+=dist[t];
        if(!distp[t])ans1.push_back(t);
        else ans2.push_back( {distp[t], t} );
        for(int j=1 ;j<=n;j++)
        {
            if(!st[j] && dist[j] > get_dist(t,j))
            {
                dist[j]=get_dist(t,j);
                distp[j]=t;
            }
        }
    }
    return res;
}
int main(){
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) scanf("%d%d", &q[i].x,&q[i].y);
    for (int i = 1; i <= n; i ++ ) scanf("%d",&wc[i]);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &wk[i]);
    LL res=prim();
    printf("%lld\n",res);
    printf("%d\n",ans1.size());
    for(auto x:ans1) printf("%d ",x);
    puts("");
    printf("%d\n", ans2.size());
    for(auto &t: ans2){
        printf("%d %d\n",t.x,t.y);
    }
    return 0;
}

 

上一篇:算法模板:dijkstra


下一篇:nginx部署vue工程简单的配置文件