题目:
问题 C: 戴森球计划
时间限制: 1.000 Sec 内存限制: 256 MB
提交 状态
题目描述
你在游戏《戴森球计划》中来到了一个新的星球,并放下了n个生产设备。虽然星球是个球形,但是可以将其抽象成一个二维平面,其中第i个生产设备的坐标为xi,yi。
你想让每个生产设备都通上电。第i个设备能通上电,当且仅当能满足以下一个条件:
- 为第i个设备建造一个火力发电厂(或者微型聚变发电站或者人造恒星),这将花费ci元;
- 选择另一个有电的设备j(j≠i),在设备i和设备j之间拉起电线(其实在游戏里是电力感应塔无线输电塔或者卫星配电站)。
设备i和设备j之间安装电线的价格是每公里ki+kj元,其中ki,kj分别是设备i和设备j的用电量。电线只能走基本方向(北、南、东、西)。电线可以相互交叉但互不影响。综上,如果设备i和设备j通过电线连接,则电线的长度为|xi−xj|+|yi−yj|公里。因此,在设备i和设备j之间拉电线的花费是(ki+kj)(|xi−xj|+|yi−yj|)。
你想用尽可能少的钱来让每个电线都能通上电。你需要输出这一最小花费。
注意:即使当i≠j时,(xi,yi)=(xj,yj)也是可能的。
输入
第一行有一个整数id(1≤id ≤20),表示测试点编号。你可以用这个信息来判断每个测试点的特殊性质(具体见数据范围与约定)。
第二行有一个整数n,意义如题。
然后有n行,第i+2行有两个整数xi和yi,表示第i个设备的坐标。
紧接着一行n个整数,分别是c1,c2,...,cn。
最后一行n个整数,分别是k1,k2,...,kn。
输出
输出一个整数,表示让所有设备通上电的最小花费。
样例输入 Copy
【样例1】 1 5 1 2 1 93 1 9 1 38 1 8 485167613 784430249 637393481 271981451 436803228 1 1 1 1 1 【样例2】 20 3 2 1 1 2 3 3 23 2 23 3 2 3
样例输出 Copy
【样例1】 271981633 【样例2】 27
提示
样例解释
在样例1中,在第4个设备建设发电站,其他4个设备通过电线连向第4个设备。
在样例2中,在第2个设备建设发电站,另外两个设备通过电线连向第2个设备,总花费是2+10+15=27。
解体思路:
1.首先要看出这是一个 (最小)生成树 森林 问题。一开始其实先是想用dfs,然后想看看dp,但都不行。以后这种题目,要学会把他抽象成数!->看到题目中有“最小花费”,又给了每个点的获得方式。一开始我不知道如何抽象成树,但仔细思考题目确实像是给了一个“森林”;例如,两个发电方式:自己发电(即为和原点连接的距离),与其他数共享发电(即与其他树连接的距离)。所以,最优解即为构成森林的边权和的最小值;
那么在此来补一下最小生成树的概念:
*给无向图,若他的某个子图中任意两个顶点都相互连通,且是一棵树,则这棵树就叫该图的最小生成树。
*若边上有权值,那么边权和最小的生成树,就叫做最小生成树(MST)。
注意:只有连通图才有最小生成树,而对于非连通图,只存在生成树森林。
例如:(懒得打字,贴图了...)
(好像跑题了,有关最小生成树还会花笔墨再拿一篇讲的...)
2.其次,用排序算法,可以得出最优解;
3.并查集在数里也用的很多,下面是摘录的网上的:
并查集(Union Find),又称不相交集合(Disjiont Set),它应用于N个元素的集合求并与查 询问题,在该应用场景中,我们通常是在开始时让每个元素构成一个单元素的集合, 然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在 哪个集合中。虽然该问题并不复杂,但面对极大的数据量时,普通的数据结构往往无 法解决,并查集就是解决该种问题最为优秀的算法。
使用森林存储集合之间的关系,属于同一集合的不同元素,都有一个相同的根节点 ,代表着这个集合。
当进行查找某元素属于哪个集合时,即遍历该元素到根节点,返回根节点所代表的集 合;在遍历过程中使用路径压缩的优化算法,使整体树的形状更加扁平,从而优化查 询的时间复杂度。 当进行合并时,即将两颗子树合为一颗树,将一颗子树的根节点指向另一颗子树的根 节点;在合并时可按子树的大小,将规模较小的子树合并到规模较大的子树上,从而 使树规模更加平衡,从而优化未来查询的时间复杂度。
原文链接:https://blog.csdn.net/lucifer_24/article/details/102295059
代码如下:(借鉴版)
#include<iostream>
#include<algorithm>
using namespace std;
int n,ans,id;
struct NODE{
int x,y,c;
}node[100010];
struct EDGE{
int u,v,l;
}edge[1000100];
int c[1000010],k[1000010],w;
int fa[100010];
//并查集的算法
int find(int i){
if(fa[i]==i){
return fa[i];
}
fa[i]=find(fa[i]);
return fa[i];
}
void unionn(int i,int j){
int fa_i=find(i);
int fa_j=find(j);
fa[fa_i]=fa_j;
}
//排序 (从小到大)
bool check(struct EDGE i,struct EDGE j){
return i.l<j.l;
}
int main()
{
cin>>id;
cin>>n;
for(int i=1;i<=n;i++){
cin>>node[i].x>>node[i].y;
}
for(int i=1;i<=n;i++){
cin>>c[i];
edge[w++]=EDGE{0,i,c[i]};
}
for(int i=1;i<=n;i++){
cin>>k[i];
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
int u=1ll*(k[i]+k[j])*(abs(node[i].x-node[j].x)+abs(node[i].y-node[j].y));
edge[w++]=EDGE{i,j,u};
}
}
sort(edge,edge+w,check);
for(int i=0;i<w;i++){
cout<<edge[i].l<<endl;
}
for(int i=1;i<=n;i++){
fa[i]=i;
}
for(int i=0;i<w;i++){
int fa_u=find(edge[i].u),fa_v=find(edge[i].v);
if(fa_u!=fa_v){
unionn(edge[i].u,edge[i].v);
ans+=edge[i].l;
}
}
cout<<ans;
return 0;
}
p.s.以后这种问题