城市通电

城市通电

平面上遍布着 $n$ 座城市,编号 $1 \sim n$。

第 $i$ 座城市的位置坐标为 $(x_i,y_i)$。

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

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

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

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

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

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

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

也就是说,如果在城市 $i$ 与城市 $j$ 之间搭建电线,则电线的长度为 $|x_i−x_j|+|y_i−y_j|$。

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

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

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

输入格式

第一行包含整数 $n$。

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

再一行包含 $n$ 个整数 $c_1,c_2, \ldots ,c_n$,用来描述每个城市建立发电站的花费。

最后一行包含 $n$ 个整数 $k_1,k_2, \ldots ,k_n$。

输出格式

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

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

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

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

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

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

数据范围

对于前三个测试点,$1 \leq n \leq 3$。
对于全部测试点,$1 \leq n \leq 2000$,$1 \leq x_i,y_i \leq {10}^{6}$,$1 \leq c_i,k_i \leq {10}^{9}$。

输入样例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(m \log m)$,其中$m = C_{n}^{2} + 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 @ 2023-03-09 14:36  onlyblues  阅读(30)  评论(0编辑  收藏  举报
Web Analytics