题目链接 : 3728. 城市通电
题目描述 :
题目分析 :
比赛的时候也想到了最小生成树,但是发现不知道如何判断最后是多少个 "树"
所以要加入一个超级源点 , 这样问题就变成了求 n + 1 个点加入 n 条边来形成最小生成树的最小开销了 而不用像上面一样考虑生成多少个树的问题了
那么初始化的时候 dis[i] 就是 村庄 i 到 超级源点的 "距离" 也就是 在 村庄 i 创建发电站的开销
用 e[i] 来表示对应 dis[i] 时, i 村庄 与 e[i] 相连
然后每次加入一个点后,更新已经确定的集合,和 dis[] 和 e[] 数组 即可
prim 算法的实现具体看下面的代码即可
本题的重点还是在于超级源点的引入
代码 :
#include <iostream>
#include <cstring>
#include <algorithm>
#include <set>
#include <vector>
using namespace std;
using LL = long long;
const int N = 2005;
const int INF = 0x3f3f3f3f;
int x[N];
int y[N];// x,y 点的坐标
int k[N];// 点的开销
bool vi[N];// 标记每一个点是否确定
int e[N];// 与 dis[N] 相联系
LL dis[N];// 每一个点与当前确定集合的最小值
int n;
LL res;// 用于记录最终的开销
/*
用 prim 算法 加入 n - 1 条边
超级源点首先建立 : 然后 dis[i] 表示剩下的点到已经确定点的距离(开销)
每次加入距离最短的点,然后更新集合 和 dis 即可
O(N^2) 的算法
*/
vector<int> ans1;// 用于存只建发电站的
vector<vector<int>> ans2;// 用于存构建的边
int get_dis(int i,int j) {
// 计算两个点之间的距离
return abs(x[i] - x[j]) + abs(y[i] - y[j]);
}
void prim() {
int cur = 0;
while(cur < n) {
cur ++;
int mm = INF;
int t = -1;
for(int i = 0;i < n;i ++) {
if(!vi[i] && dis[i] < mm) {
mm = dis[i];
t = i;
}
}
if(e[t] == n) {
// 创建发电站
vi[t] = true;
res += dis[t];
ans1.push_back(t + 1);
} else {
// 创建边
vi[t] = true;
res += dis[t];
ans2.push_back({t + 1,e[t] + 1});
}
// 更新集合
for(int i = 0;i < n;i ++) {
if(!vi[i]) {
LL nc = ((LL)k[t] + k[i]) * get_dis(t,i);
if(nc < dis[i]) {
dis[i] = nc;
e[i] = t;
}
}
}
}
return ;
}
int main()
{
cin >> n;
res = 0;
for (int i = 0; i < n; i ++ )
scanf("%d%d", &x[i], &y[i]);
for (int i = 0; i < n; i ++ ) {
scanf("%lld", &dis[i]);
vi[i] = false;
e[i] = n;
}
vi[n] = true;
// 加入第 n 个点 作为超级源点 dis[n] = 0
ans1.clear();
ans2.clear();
for (int i = 0; i < n; i ++ )
scanf("%d", &k[i]);
prim();
cout<<res<<endl;
cout<<ans1.size()<<endl;
for(auto& i : ans1) {
cout<<i<<" ";
}
cout<<endl;
cout<<ans2.size()<<endl;
for(auto& i : ans2) {
cout<<i[0]<<" "<<i[1]<<endl;
}
return 0;
}