畅通工程再续
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 33067 Accepted Submission(s): 10887
Problem Description
相信大家都听说一个“百岛湖”的地方吧,百岛湖的居民生活在不同的小岛中,当他们想去其他的小岛时都要通过划小船来实现。现在政府决定大力发展百岛湖,发展首先要解决的问题当然是交通问题,政府决定实现百岛湖的全畅通!经过考察小组RPRush对百岛湖的情况充分了解后,决定在符合条件的小岛间建上桥,所谓符合条件,就是2个小岛之间的距离不能小于10米,也不能大于1000米。当然,为了节省资金,只要求实现任意2个小岛之间有路通即可。其中桥的价格为 100元/米。
Input
输入包括多组数据。输入首先包括一个整数T(T <= 200),代表有T组数据。
每组数据首先是一个整数C(C <= 100),代表小岛的个数,接下来是C组坐标,代表每个小岛的坐标,这些坐标都是 0 <= x, y <= 1000的整数。
每组数据首先是一个整数C(C <= 100),代表小岛的个数,接下来是C组坐标,代表每个小岛的坐标,这些坐标都是 0 <= x, y <= 1000的整数。
Output
每组输入数据输出一行,代表建桥的最小花费,结果保留一位小数。如果无法实现工程以达到全部畅通,输出”oh!”.
Sample Input
2210 1020 2031 12 21000 1000
Sample Output
1414.2oh!
Author
8600
Source
最小生成树问题,和1233题类似,不同的是边的大小需要自己算出来
方法一:kruskal算法
算法过程:
1.将图各边按照权值进行排序
2.将图遍历一次,找出权值最小的边,(条件:此次找出的边不能和已加入最小生成
树集合的边构成环),若符合条件,则加入最小生成树的集合中。不符合条件则继
续遍历图,寻找下一个最小权值的边。
3.递归重复步骤1,直到找出n-1条边为止(设图有n个结点,则最小生成树的边数应
为n-1条),算法结束。得到的就是此图的最小生成树。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
using namespace std;
struct Node{
int s,e;
double dis;
}v[10010];
struct land{
int x,y;
}a[110];
int len,pre[110],n,T;
void init(){
for(int i = 0;i < 110;i++) pre[i] = i;
len = 0;
}
int find(int root){
if(root == pre[root]) return root;
else return pre[root] = find(pre[root]);
}
bool join(int a,int b){
int x = find(a);
int y = find(b);
if(x != y){
pre[x] = y;
return true;//返回该边能加进去
}
return false;//返回该边能加进去
}
bool cmp(Node &a,Node &b){
return a.dis < b.dis;
}
void kruskal(){
double sum = 0;
for(int i = 0;i < len;i++){
if(v[i].dis<=1000&&v[i].dis>=10&&join(v[i].s,v[i].e)){//不符合要求的点不能加进去
sum+=v[i].dis;
}
}
int cnt = 0;
for(int i = 0;i < n;i++){
if(pre[i] == i) cnt++;
}
if(cnt > 1){
printf("oh!\n");
}else{
printf("%.1lf\n",sum*100);
}
}
int main(){
// freopen("input.txt","r",stdin);
scanf("%d",&T);
while(T--){
init();
scanf("%d",&n);
for(int i = 0;i < n;i++){
scanf("%d%d",&a[i].x,&a[i].y);
}
for(int i = 0;i < n;i++){//添加所有的的边
for(int j = i+1;j<n;j++){
v[len].e = j;
v[len].s = i;
v[len++].dis = sqrt((a[i].x-a[j].x)*(a[i].x-a[j].x)+(a[i].y-a[j].y)*(a[i].y-a[j].y));
}
}
sort(v,v+len,cmp);//按边的长度从小到大排序
kruskal();
}
return 0;
}
方法二:
普利姆(Prime)算法(只与顶点相关)
算法描述:
普利姆算法求最小生成树时候,和边数无关,只和定点的数量相关,所以适合求稠
密网的最小生成树,时间复杂度为O(n*n)。
算法过程:
1.将一个图的顶点分为两部分,一部分是最小生成树中的结点(A集合),另一部分
是未处理的结点(B集合)。
2.首先选择一个结点,将这个结点加入A中,然后,对集合A中的顶点遍历,找出A中
顶点关联的边权值最小的那个(设为v),将此顶点从B中删除,加入集合A中。
3.递归重复步骤2,直到B集合中的结点为空,结束此过程。
4.A集合中的结点就是由Prime算法得到的最小生成树的结点,依照步骤2的结点连接
这些顶点,得到的就是这个图的最小生成树。
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int T,n;
#define INF 999999
double map[110][110],dis[110];
bool use[110];
struct node{
int x,y;
}a[110];
void prim(){
double sum = 0;
use[0] = true;
double MIN;
int temp;
for(int i = 1;i < n;i++) dis[i] = map[0][i];
for(int i = 1;i < n;i++){
temp = i,MIN = INF;
for(int j = 1;j < n;j++){
if(!use[j]&&MIN > dis[j]&&dis[j]>=10&&dis[j]<=1000){
MIN = dis[j];
temp = j;
}
}
if(temp == i){
if(!use[i]&&dis[i]>=10&&dis[i]<=1000){
sum+=dis[i];
use[i] = true;
}else{
if(!use[i]){
printf("oh!\n");
return ;
}
}
}else{
use[temp] = true;
sum += MIN;
}
for(int j = 1;j < n;j++){
if(!use[j]&&dis[j] > map[j][temp]&&map[j][temp]>=10&&map[j][temp]<=1000){
dis[j] = map[j][temp];
}
}
}
int cnt = 0;
for(int i = 0;i < n;i++){
if(!use[i]) cnt++;
}
if(cnt > 0){
printf("oh!\n");
}else{
printf("%.1lf\n",sum*100);
}
}
int main(){
// freopen("input.txt","r",stdin);
scanf("%d",&T);
while(T--){
memset(use,false,sizeof(use));
scanf("%d",&n);
for(int i = 0;i < n;i++){
scanf("%d%d",&a[i].x,&a[i].y);
// printf("%d %d\n",a[i].x,a[i].y);
}
for(int i = 0;i < n;i++){
map[i][i] = 0;
for(int j = i+1;j < n;j++){
map[i][j] = map[j][i] = sqrt(1.0*(a[i].x-a[j].x)*(a[i].x-a[j].x)+(a[i].y-a[j].y)*(a[i].y-a[j].y));
if(map[i][j]<10||map[i][j]>1000){
map[i][j] = map[j][i] = INF;
}
// cout<<map[i][j]<<" ";
}
}
prim();
}
return 0;
}