城市通电

城市通电

平面上遍布着 n 座城市,编号 1n

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

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

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

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

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

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

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

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

也就是说,如果在城市 i 与城市 j 之间搭建电线,则电线的长度为 |xixj|+|yiyj|

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

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

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

输入格式

第一行包含整数 n

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

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

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

输出格式

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

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

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

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

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

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

数据范围

对于前三个测试点,1n3
对于全部测试点,1n20001xi,yi1061ci,ki109

输入样例1:

3
2 3
1 1
3 2
3 2 3
3 2 3

输出样例1:

8
3
1 2 3 
0

输入样例2:

3
2 1
1 2
3 3
23 2 23
3 2 3

输出样例2:

27
1
2 
2
1 2
2 3

 

解题思路

  看上去像是个求最小生成树的问题,但实际上要保证每个城市通电不一定是一棵树的形式,只要每个连通块内存在一个点是发电站就可以。有个trick就是建立一个超级源点,如果某个点是发电站,那么这个点就与超级源点连一条边,边的权重就是在这个点建立发电站的代价,这样就把若干个连通块变成一个连通块,变成了求最小生成树的问题。

  对于原图中的每一个合法方案,由于每一个连通块中的点都与发电站连通,而在新图中每个发电站都与超级源点相连,因此每个点都与超级源点连通,因此就对应一颗生成树(原图中不存在环,否则去掉某条边代价会变小,因此得到的连通图是一颗生成树)。因此原图中的每一个方案都对应新图中的一棵生成树。对于原图中每个方案的代价,在新图中与之对应的代价也是一样的。反过来在新图中的每个方案,如果某个点与超级源点相连,那么在这个点建发电站,否则其余的边不变,代价与对应的原图也是一样的。因此两个集合中的元素是一一对应的,求原图中方案的最小值就等价于求新图中方案的最小值。而新图中的最小值就是所有生成树中的最小生成树。

  这里求最小生成树用 Kruskal,时间复杂度为O(mlogm),其中m=Cn2+n

  AC代码如下:

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 typedef long long LL;
 5 
 6 const int N = 2010, M = N * N;
 7 
 8 struct Node {
 9     int v, w;
10     LL wt;
11     
12     bool operator<(Node &t) {
13         return wt < t.wt;
14     }
15 }e[M];
16 int x[N], y[N];
17 int a[N], b[N];
18 int fa[N];
19 
20 int find(int x) {
21     return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
22 }
23 
24 int main() {
25     int n;
26     scanf("%d", &n);
27     for (int i = 1; i <= n; i++) {
28         scanf("%d %d", x + i, y + i);
29     }
30     int sz = 0;
31     for (int i = 1; i <= n; i++) {
32         scanf("%d", a + i);
33         e[sz++] = {0, i, a[i]}; // 设第0个点为超级源点,发电站与0号点连一条边
34     }
35     for (int i = 1; i <= n; i++) {
36         scanf("%d", b + i);
37     }
38     for (int i = 1; i <= n; i++) {  // 完全图,每两个点建一条边
39         for (int j = 1; j < i; j++) {
40             e[sz++] = {i, j, (LL)(abs(x[i] - x[j]) + abs(y[i] - y[j])) * (b[i] + b[j])};
41         }
42     }
43     sort(e, e + sz);
44     for (int i = 0; i <= n; i++) {
45         fa[i] = i;
46     }
47     LL ret = 0;
48     vector<int> ans1;
49     vector<vector<int>> ans2;
50     for (int i = 0; i < sz; i++) {  // 求最小生成树
51         int v = e[i].v, w = e[i].w;
52         LL wt = e[i].wt;
53         int pv = find(v), pw = find(w);
54         if (pv != pw) {
55             fa[pv] = pw;
56             ret += wt;
57             if (!v) ans1.push_back(w);
58             else ans2.push_back({v, w});
59         }
60     }
61     printf("%lld\n%d\n", ret, ans1.size());
62     for (auto &x : ans1) {
63         printf("%d ", x);
64     }
65     printf("\n%d\n", ans2.size());
66     for (auto &p : ans2) {
67         printf("%d %d\n", p[0], p[1]);
68     }
69     
70     return 0;
71 }
复制代码

 

参考资料

  AcWing 3728. 城市通电(蓝桥杯集训·每日一题):https://www.acwing.com/video/4650/

posted @   onlyblues  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示