城市通电:

文章讲述了如何运用Prim算法解决一个结合了发电站建设和电线连接的城市通电问题。首先,建立发电站是通电的前提,然后使用最小生成树策略优化电线连接以降低成本。在可能存在多个连通块的情况下,通过建立虚拟源点来转化问题,并最终计算出最小总花费。代码示例展示了如何实现这一过程。
摘要由CSDN通过智能技术生成

思路:

由题意可知,每个城市通电的话有两种方法:一种是用电线连接,另一种是采用建立发电站连接。而假设只有用电线连接的话,要使得花费最小,那么这就很明显是采用我们的最小生成树算法进行求解。但问题是还存在建立发电站供电的要求,建立了发电站你才能够发电啊,此时连接两座城市的电线才有效啊。所以前提是先考虑建立发电站的情况。

另外就是,最后的图可能不是一个连通块(因为只要每个连通块内都包含一个发电站即可。),而是多个连通块的情况,那么若是多个连通块,那么每个连通块都最少有一个发电站。然后要我们求最小花费。所以这里该做一个变形:如果一个城市建立电站的话,那么我们可以建立一个虚拟源点,然后每座城市都向源点连接一条边,边权是:该城市建立发电站的花费。从而枚举所有的情况。 这是等价的在原问题里的村庄建立发电站和在转化后的问题里建立一个虚拟源点,建立发电站的花费等价,是能一一对应的。所以可以转化问题。

建立虚拟源点0号点,使得0号点与每个城市都连通。然后

代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

#define x first
#define y second
using namespace std;
typedef pair<int, int> PII; //记录坐标!
typedef long long LL;
const int N = 2e3 + 10;

PII q[N];   //记录每个城市的坐标
int wc[N], wk[N];   //分别表示每座城市的电站花费和电线花费!
int n;
LL dist[N]; //dist数组表示该点距离生成树集合的最短距离。
bool st[N]; //prim算法还需开一个bool数组表示每个点是否在生成树集合种。
vector<int> ans1;   //存储在哪些点建立电站。
vector<PII> ans2;   //哪两个点之间建立电线
int fa[N];          //存储记录每个点的父节点前驱节点。

LL get_cost(int a, int b)
{
    int dx = abs(q[a].x - q[b].x);
    int dy = abs(q[a].y - q[b].y);
    return (dx + dy)*1ll*(wk[a]+wk[b]); 
}

LL prim()
{
    memset (dist, 0x3f, sizeof (dist));
    dist[0] = 0;
    st[0] = true;   //本题中虚拟源点是已经确立了答案,即在集合中了
    for (int i=1; i <= n; i ++) dist[i] = wc[i];    //每个点到自己的距离
    

    LL res = 0;     //表示我们的总代价是多少。
    for (int i=0; i < n; i ++)
    {
        int t = -1;
        for (int j=1; j <= n; j ++)
            if (!st[j] && (dist[j] < dist[t] || t == -1))
                t = j;
        
        res += dist[t];
        st[t] = true;
        if (!fa[t]) ans1.push_back(t);
        else ans2.push_back({fa[t], t});
        for (int j=1; j <= n; j ++)
            if (dist[j] > get_cost(t, j))   //如果在j点建立电站的花费 > 两点之间的建立电线的花费的话
            {
                dist[j] = get_cost(t, j);   //则选择建立电线!
                fa[j] = t;                  //建立关系
            }
            
    }
    return res;
}

int main()
{
    cin >> n;
    for (int i=1;  i <= n; i ++)
        scanf("%d%d", &q[i].x, &q[i].y);    //输入第i座城市的坐标!
    
    //输入每座城市的建立发电站的花费 和 连接电线的花费!
    for (int i=1; i <= n; i ++)
        scanf("%d", &wc[i]);
    for (int i=1; i <= n; i ++)
        scanf("%d", &wk[i]);
    
    //将两两城市之间拉电线的操作看作是连接边,边权为:(abs(x2-x1)+abs(y2-y1))*(wk[2] + wk[1]);
    printf ("%lld\n", prim());
    printf ("%d\n", (int)ans1.size());
    for (int i=0; i < ans1.size(); i ++)
        printf ("%d ", ans1[i]);
    puts("");
    printf ("%d\n", (int)ans2.size());
    for (auto& [x, y]:ans2)
    {
        printf ("%d %d\n", x, y);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值