[最小生成树] aw3728. 城市通电(最小生成树+prim算法求具体方案+超级源点+aw周赛005_3)
Posted Ypuyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[最小生成树] aw3728. 城市通电(最小生成树+prim算法求具体方案+超级源点+aw周赛005_3)相关的知识,希望对你有一定的参考价值。
1. 题目来源
链接:3728. 城市通电
相似题:1145. 北极通讯网络
2. 题目解析
好题。
最小生成树的题型不多,在此是超级源点的应用。
思路:
- 发电站和电线抽象为一张无向图,电线就是将两点相连。
- 那么最终应该是将整张图分成了一个个连通块,每个连通块中有一个点建立成为发电站。
- 考虑新建一个超级源点,所有的发电站都直接连向超级源点。
- 起初,所有的点都与这个超级源点相连,在某点建立发电站就相当于选择保留超级源点到该点的这条边。
- 最终,在整个图中选择若干边,使得整张图连通。其中与超级源点直接相连的点就是发电站需要建立在该点上。
- 故,问题转化为最小生成树问题中的超级源点的应用。
细节:
- 本题是稠密图,给了
n
个点,建图时需要将任意两点都需要连一条边。 故采用prim()
算法会好点, O ( n 2 ) O(n^2) O(n2)。如果采用kruskal
算法的话,时间复杂度偏高, O ( m l o g n ) O(mlogn) O(mlogn),其中 m 2 = n m^2=n m2=n,那么就是 O ( n 2 l o g n ) O(n^2logn) O(n2logn) 的时间了。所以针对稠密图来讲,时间使用prim
是最优选择,同理,稠密图的最短路算法,使用dijkstra()
算法也是更好的选择。 - 2000 个点,最多 2000 条边,花费可能爆
int
,需要使用long long
来存答案。
注意用 long long
。
这个知识点,还没学过,但也马上就要学了。有个类似的题 1145. 北极通讯网络 但是没做过,还没学到。
时间复杂度: O ( n l o g x ) O(nlogx) O(nlogx)
空间复杂度: O ( n ) O(n) O(n)
// 类似题 1145. 北极通讯网络
// Kruskal 需要建图,任意两点之间连边,在此不太适用,日后补吧,反正也忘了
#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 = 2005;
int n;
PII q[N]; // 存所有点
int wc[N], wk[N]; // 权值
LL dist[N]; // 存点到集合的距离,曼哈顿距离*k 会爆 int
int distp[N]; // 存方案,当前点连向连通块中的点编号
bool st[N];
vector<int> res1; // 存直接与超级源点相连的点,即为发电站
vector<PII> res2; // 最小生成树中的边,两点相连
// 求两点之间的距离
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() {
memset(dist, 0x3f, sizeof dist); // 可以省略,超级源点的初始化会对该数组重新赋值
dist[0] = 0, st[0] = true; // 超级源点初始化
for (int i = 1; i <= n; i ++ ) dist[i] = wc[i]; // 在此,dist[i] 不必用 LL,memset 0x3f 也不需要,会被覆盖掉
LL res = 0;
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
t = j;
st[t] = true; // 将该点选入最小生成树中
res += dist[t]; // 答案累加该边到集合的长度
if (!distp[t]) res1.push_back(t); // 如果 t 直接连向超级源点,则 t 为发电站
else res2.push_back({distp[t], t}); // 否则,t 连向普通点
for (int j = 1; j <= n; j ++ ) { // 用 t 更新其余点到集合的距离
if (!st[j] && dist[j] > get_dist(t, j)) { // 这里是到集合的距离,与 dijkstra() 的最重要区别
dist[j] = get_dist(t, j); // t 更新 j
distp[j] = t; // 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", res1.size());
for (auto e : res1) printf("%d ", e);
puts("");
printf("%d\\n", res2.size());
for (auto e : res2) printf("%d %d\\n", e.x, e.y);
return 0;
}
以上是关于[最小生成树] aw3728. 城市通电(最小生成树+prim算法求具体方案+超级源点+aw周赛005_3)的主要内容,如果未能解决你的问题,请参考以下文章