题目介绍
魔法阵.
题目描述
平面上遍布着 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 定义