AcWing第 5 场周赛

AcWing 3728. 城市通电(南昌理工学院)

题目介绍

魔法阵.
题目描述
平面上遍布着 n 座城市,编号 1∼n。

第 i 座城市的位置坐标为 (xi,yi)。

不同城市的位置有可能重合。

现在要通过建立发电站和搭建电线的方式给每座城市都通电。

一个城市如果建有发电站,或者通过电线直接或间接的与建有发电站的城市保持连通,则该城市通电。

在城市 i 建立发电站的花费为 ci 元。

在城市 i 与城市 j 之间搭建电线所需的花费为每单位长度 ki+kj 元。

电线只能沿上下左右四个方向延伸,电线之间可以相互交叉,电线都是双向的。

每根电线都是由某个城市沿最短路线搭建到另一个城市。

也就是说,如果在城市 i 与城市 j 之间搭建电线,则电线的长度为 |xi−xj|+|yi−yj|(|…|表示绝对值)。

请问,如何合理设计通电方案,可以使得所有城市都成功通电,且花费最少?

输出最少花费和具体方案。

如果方案不唯一,则输出任意一种合理方案均可。

输入格式
第一行包含整数 n。

接下来 n 行,其中第 i 行包含两个整数 xi,yi,用来描述城市 i 的横纵坐标。

再一行包含 n 个整数 c1,c2,…,cn,用来描述每个城市建立发电站的花费。

最后一行包含 n 个整数 k1,k2,…,kn。

输出格式
第一行输出所需要的最少花费。

第二行输出一个整数 v,表示需要建立发电站的数量。

第三行输出 v 个整数,表示建立发电站的城市编号,注意输出编号要在范围 [1,n] 内。且输出编号不应重复。输出编号顺序随意。

第四行输出一个整数 e,表示需要搭建的电线数量。

接下来 e 行,每行输出两个整数 a,b,表示要在城市 a 和 b 之间搭建电线。注意,任意两个城市之间最多只需要搭建一根电线,也就是说,对于每个 (a,b),不要有多余的 (a,b) 或 (b,a) 输出。a 和 b 不能相同,且要在范围 [1,n] 内。输出电线顺序随意。

如果答案不唯一,输出任意合理方案即可。

数据范围
对于前三个测试点,1 ≤ n ≤ 3。
对于全部测试点,1 ≤ n ≤ 2000,1 ≤xi, yi ≤ 10的6次方,1 ≤ ci, ki ≤10的9次方。

输入样例1:
3
2 3
1 1
3 2
3 2 3
3 2 3

输入样例2:
3
2 1
1 2
3 3
23 2 23
3 2 3

解题思路

题目的意思
给我们n个村庄,编号是1-n,村庄要生活,他得用电吧,没有电,搞电有两种法子,第一我们自己搞电,咋的搞?我们自己建一个发电站,花费是c[i]。第二种 ,我们去 拿电,从哪里 拿呢?不是有人有发电站吗?嘿嘿嘿嘿,但是呀,从其他村庄那里拿电还是要花费,得买电线,每单位的电线是ki+kj ,长度是 |xi−xj|+|yi−yj|(|…|表示绝对值),聪明的我们怎么花费最少呢?

我们可以发现我们要将所有村庄通上电,等于所有点即村庄是在一个连通块里,那么这么看就是一个最小生成树的问题(狗头),那怎么构图呢?边又是什么呢?

我们把第一种建设发电站的方法,看成有一超级发电站,它是零点,如果有村庄要选择第一种发电方式,就是和我(零点)相连,花费c[i]
第二种就是村庄之间相连接,花费(ki+kj)* |xi−xj|+|yi−yj|
这两种方式就是建立两点之间边的方式
问题又来了
我们还得知道有哪些村庄是建立发电站的,那些村庄是与其他村子相连的,于是我们用
vector < int > ans1;//连向超级发电站的点
vector< PII> ans2;//村庄和村庄之间的边

ps:我们面对比较长的题目要有一定的耐心(看长题目真的狂躁)
上代码

代码实现

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

#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];//描述每个城市建立发电站的花费
int wk[N];//在城市 i 与城市 j 之间搭建电线所需的花费为每单位长度 ki+kj 元
int dist[N];//城市建立发电站的花费
int distp[N];//连向联通块的点
bool st[N]; //是否在联通块中
vector<int> ans1;//连向超级发电站的点
vector<PII> ans2;//村庄和村庄之间的边

inline LL get_distance(int a,int b) //求两个点之间相连的花费,由于函数调用多次所以用inline加快速度
{
    int x=q[a].x-q[b].x;
    int y=q[a].y-q[b].y;
    return (LL) (abs(x)+abs(y)) * ( wk[a]+wk[b]);
}

LL prim()
{
    LL res=0;
    dist[0] = 0; //假设0点是超级发电站,所有要建发电站的点连这个点
    st[0]=true;//将超级发电站放入联通块
    for(int i = 1;i <= n;i ++) dist[i]=wc[i];//假设每个点即村庄全部都连向超级发电站
    
    for (int i = 0; i < n; i ++ )//将剩余的n个点加入联通块里
    {
        int t=-1; //表示把点加入连通块
        for (int j = 1; j <= n; j ++ )//找到距离联通块最近的点
            if(!st[j] && (t==-1 || dist[j]<dist[t]))
                t = j;
        st[t]=true;
        res += dist[t];
        if(!distp[t]) ans1.push_back(t);//如果连向t点的这个点是零点的话,那么就加入ans1中
        else ans2.push_back({distp[t],t});//否则,就是连向村庄
        for (int j = 1; j <= n; j ++ )//更新其他所有点的花费
            if(!st[j] && dist[j] > get_distance(t,j))
            {
                dist[j]=get_distance(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;
}

ps:为什么dist数组不是long long 而是 int 型呢? 因为一开始我们假设都是连向超级发电站的点,而其花费是不会超过int,只有比wc[i]即连向超级发电站的花费更少,才会更新,所以不必使用long long 定义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值