Problem Description

A group of explorers has found a solitary island. They land on the island and explore it along a straight line. They build a lot of campsites while they advance. So the campsites are laid on the line.

Coincidently, another group of explorers land on the island at the same time. They also build several campsites along another straight line. Now the explorers meet at the island and they decide to connect all the campsites with telegraph line so that they can communicate with each other wherever they are.

Simply building segments that connect a campsite to another is quite easy, but the telegraph line is rare. So they decide to connect all the campsites with as less telegraph line as possible. Two campsites are connected if they are directly connected with telegraph line or they are both connected to another campsite.

Input

There are multiple test cases.

The number of the test cases is in the first line of the input.

For each test case, first line contains two integers N and M (0≤N, M≤10000), which N is the number of the campsites of the first group of explorers and M is the number of the campsites of the second group of explorers. And there exist at least one campsite.

The next two lines contain eight integers Ax, Ay, Bx, By, Cx, Cy, Dx, Dy. Their absolute values are less than 1000. The integers are the coordinates of four points A, B, C and D. The exploring path of the first group is begin with the first point A and end with the second point B, and the path of the second group is from the third point C to the fourth point D. Every pair of points is distinct.

The last two lines of the test case contain N and M real numbers; they indicate the positions of the campsites. Suppose the i-th real number in the first line is t. It means the x-coordinate of the i-th campsite is Ax * t + Bx * (1-t), and the y-coordinate is Ay * t + By * (1-t). Equally, the campsite on the second straight line is C * t + D * (1-t). You can assume that there are at most four digits in the decimal part, and the numbers are always between 0 and 1.

Output

For each test case, output contains only a real number rounded to 0.001.

Sample Input

1

4 4

0 0 10 10

0 10 10 0

0.1 0.3 0.6 0.8

0.1 0.3 0.6 0.8

Sample Output

Case #1: 19.638

分析:

此题是一道最小生成树的模板题,但题目较复杂,我们采用分步的方法处理问题

1.处理点

为了处理点的方便,我们可以编写结构体,使点的处理更简洁。还有一点细节,一条直线上的同一个位置可能会有多个营地,所以要判重

struct point {
double x;//x坐标
double y;//y坐标
int id;//点的编号(之后要用)
} p[MAXN],q[MAXN];
point make_point(double x,double y,int id) {//初始化
point tmp;
tmp.x=x;
tmp.y=y;
tmp.id=id;
return tmp;
}
void input(point* a) {//输入
scanf("%lf%lf",&(a->x),&(a->y));
}
double getdis(point a,point b) {//两点之间距离公式
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

2.建图

时间方面:如果我们把每个点和其他所有点都连起来,时间复杂度是\(O((n+m)^2)\),肯定是不行的,因此我们先将同一条直线上的每一个点连起来,然后对于一条直线上的点,三分出距离另一条直线最近的两个点进行连边。为了防止特殊情况,也把这两个点旁边的两个点连接起来。

int l,r;
l=0;
r=m-1;
while(r-l>1) {//三分过程
int mid1=(r+l)/2;
int mid2=(mid1+r)/2;
if(getdis(p[i],q[mid1])>getdis(p[i],q[mid2])) l=mid1;
else r=mid2;
}

空间方面:用邻接矩阵的话空间会超出限制,于是采用邻接表进行存储

struct edge_table {
int from;//起点
int to;//终点
double value;//长度
} edge[MAXN*32];
int edge_cnt=0;//数边的条数
void add_edge(int u,int v,double w) {
edge[++edge_cnt].from=u;
edge[edge_cnt].to=v;
edge[edge_cnt].value=w;
}

3.最小生成树

笔者一开始使用的是堆优化的prim算法,理论上时间复杂度应为\(O(nlog_2n)\),但不知道由于什么原因会TLE,使用Kruskal算法则可AC

TLE代码:

struct edge_table {
int from;
int to;
//int next;
double value;
} edge[MAXN*32];
int head[MAXN+MAXN];
int edge_cnt=0;
void add_edge(int u,int v,double w) {
edge[++edge_cnt].from=u;
edge[edge_cnt].to=v;
edge[edge_cnt].value=w;
edge[edge_cnt].next=head[u];
head[u]=edge_cnt;
} struct heap_node{//建堆
int id;
double value;
friend bool operator <(heap_node a,heap_node b){
return a.value>b.value;
}
}; double key[MAXN+MAXN];
int used[MAXN+MAXN];
double prim(){
double ans=0;
int tot=0;
memset(key,0x7f,sizeof(key));
memset(used,0,sizeof(used));
priority_queue<heap_node>heap;
heap_node now,nex;
now.id=1;
now.value=key[1]=0;
heap.push(now);
while(!heap.empty()){
now=heap.top();
heap.pop();
int u=now.id;
if(now.value!=key[u]) continue;
used[u]=1;
ans+=key[u];
tot++;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(used[v]==0&&key[v]>edge[i].value){
key[v]=edge[i].value;
nex.value=key[v];
nex.id=v;
heap.push(nex);
}
}
}
if(tot<n+m) ans=-1;
return ans;
}

AC代码:

int father[MAXN];//建立并查集
int find(int x) {//并查集的查找函数
if(father[x]!=x) father[x]=find(father[x]);//路径压缩
return father[x];
}
int comp(edge_table a,edge_table b) {//按边的长度从小到大排序
return a.value<b.value;
}
double kruskal() {
for(int i=0; i<=n+m; i++) father[i]=i;//初始化并查集,让每一个点自成一个独立的连通分量
double sum=0;
sort(edge+1,edge+edge_cnt+1,comp);//按边的长度从小到大排序
for(int i=1; i<=edge_cnt; i++) {
int tx=find(edge[i].from);
int ty=find(edge[i].to);
if(tx!=ty) {//如果两个点在两个不同的连通分量
father[tx]=ty;//合并两个连通分量
sum+=edge[i].value;//将边加入最小生成树
}
}
return sum;
}

代码:

全部代码如下:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define MAXN 50005
using namespace std;
struct point {
double x;//x坐标
double y;//y坐标
int id;//点的编号(之后要用)
} p[MAXN],q[MAXN];
point make_point(double x,double y,int id) {//初始化
point tmp;
tmp.x=x;
tmp.y=y;
tmp.id=id;
return tmp;
}
void input(point* a) {//输入
scanf("%lf%lf",&(a->x),&(a->y));
}
double getdis(point a,point b) {//两点之间距离公式
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
} struct edge_table {
int from;//起点
int to;//终点
double value;//长度
} edge[MAXN*32];
int edge_cnt=0;//数边的条数
void add_edge(int u,int v,double w) {
edge[++edge_cnt].from=u;
edge[edge_cnt].to=v;
edge[edge_cnt].value=w;
}
int n,m;
double t1[MAXN],t2[MAXN];
void delete_same() {//判重函数
sort(t1,t1+n);
sort(t2,t2+m);
int ptr=0;
for(int i=0; i<n; i++) { //判重过程,可在纸上模拟,方便理解
if(i==n-1||t1[i]!=t1[i+1]) t1[ptr++]=t1[i];
}
n=ptr;//将n重置为判重后点的数目
ptr=0;
for(int i=0; i<m; i++) {
if(i==m-1||t2[i]!=t2[i+1]) t2[ptr++]=t2[i];
}
m=ptr;
} int father[MAXN];//建立并查集
int find(int x) {//并查集的查找函数
if(father[x]!=x) father[x]=find(father[x]);//路径压缩
return father[x];
}
int comp(edge_table a,edge_table b) {//按边的长度从小到大排序
return a.value<b.value;
}
double kruskal() {
for(int i=0; i<=n+m; i++) father[i]=i;//初始化并查集,让每一个点自成一个独立的连通分量
double sum=0;
sort(edge+1,edge+edge_cnt+1,comp);//按边的长度从小到大排序
for(int i=1; i<=edge_cnt; i++) {
int tx=find(edge[i].from);
int ty=find(edge[i].to);
if(tx!=ty) {//如果两个点在两个不同的连通分量
father[tx]=ty;//合并两个连通分量
sum+=edge[i].value;//将边加入最小生成树
}
}
return sum;
}
int main() {
point a,b,c,d;
int cnt;
scanf("%d",&cnt);
for(int cas=1; cas<=cnt; cas++) {
int i;
scanf("%d %d",&n,&m);
input(&a);
input(&b);
input(&c);
input(&d);
for(i=0; i<n; i++) scanf("%lf",&t1[i]);
for(i=0; i<m; i++) scanf("%lf",&t2[i]);
delete_same();
for(i=0; i<n; i++) {//将点加入
p[i]=make_point(a.x*t1[i]+b.x*(1-t1[i]),a.y*t1[i]+b.y*(1-t1[i]),i+1);
}
for(i = 0 ; i < m ; i++) {
q[i]=make_point(c.x*t2[i]+d.x*(1-t2[i]),c.y*t2[i]+d.y*(1-t2[i]),i+n+1);
} edge_cnt=0;
double sum1=0,sum2=0;
for(i=0; i<n-1; i++) {//将每条直线上的点连起来
double l=getdis(p[i],p[i+1]);
add_edge(p[i].id,p[i+1].id,l);//由于是无向图,每条边存两遍
add_edge(p[i+1].id,p[i].id,l);
sum1+=l;
}
for(i=0; i<m-1; i++) {
double l=getdis(q[i],q[i+1]);
add_edge(q[i].id,q[i+1].id,l);
add_edge(q[i+1].id,q[i].id,l);
sum2+=l;
}
if(n==0||m==0) {//如果只有一条直线的特判
printf("Case #%d: %.3lf\n",cas,sum1+sum2);
continue;
} for(int i=0; i<n; i++) {
int l,r;
l=0;
r=m-1;
while(r-l>1) {//三分过程
int mid1=(r+l)/2;
int mid2=(mid1+r)/2;
if(getdis(p[i],q[mid1])>getdis(p[i],q[mid2])) l=mid1;
else r=mid2;
}
add_edge(p[i].id,q[l].id,getdis(p[i],q[l]));//将距离最短的两条边加入邻接表
add_edge(q[l].id,p[i].id,getdis(p[i],q[l]));
add_edge(p[i].id,q[r].id,getdis(p[i],q[r]));
add_edge(q[r].id,p[i].id,getdis(p[i],q[r]));
if(l-1>=0) {//将距离第二短的两条边加入邻接表
add_edge(p[i].id,q[l-1].id,getdis(p[i],q[l-1]));
add_edge(q[l-1].id,p[i].id,getdis(p[i],q[l-1]));
}
if(r+1<=m-1) {
add_edge(p[i].id,q[r+1].id,getdis(p[i],q[r+1]));
add_edge(q[r+1].id,p[i].id,getdis(p[i],q[r+1]));
} }
double sum=kruskal();
printf("Case #%d: %.3lf\n",cas,sum);
}
}

HDU 3228 题解(最小生成树)(Kruskal)(内有详细注释)的更多相关文章

  1. HDU 4786(最小生成树 kruskal)

    题目链接:pid=4786" target="_blank">http://acm.hdu.edu.cn/showproblem.php?pid=4786 Prob ...

  2. DirectShow中写push模式的source filter流程 + 源代码(内附详细注释)

    虽然网上已有很多关于DirectShow写source filter的资料,不过很多刚开始学的朋友总说讲的不是很清楚(可能其中作者省略了许多他认为简 单的过程),读者总希望看到象第一步怎么做,第二步怎 ...

  3. 详细注释!二维码条码扫描源码,使用Zxing core2.3

    from:http://www.eoeandroid.com/forum.php?mod=viewthread&tid=325529&page=1 现如今很多项目中都会应用到条码扫描解 ...

  4. 最小生成树——Kruskal算法理解

    背景:本文是在小甲鱼数据结构教学视频中的代码的基础上,添加详细注释而完成的.该段代码并不完整,仅摘录了核心算法部分,结合自己的思考,谈谈理解. Prim算法理解: 如图(摘录自小甲鱼教学视频中的图片) ...

  5. 【转】最小生成树——Kruskal算法

    [转]最小生成树--Kruskal算法 标签(空格分隔): 算法 本文是转载,原文在最小生成树-Prim算法和Kruskal算法,因为复试的时候只用到Kruskal算法即可,故这里不再涉及Prim算法 ...

  6. hdu Constructing Roads (最小生成树)

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=1102 /************************************************* ...

  7. 模板——最小生成树kruskal算法+并查集数据结构

    并查集:找祖先并更新,注意路径压缩,不然会时间复杂度巨大导致出错/超时 合并:(我的祖先是的你的祖先的父亲) 找父亲:(初始化祖先是自己的,自己就是祖先) 查询:(我们是不是同一祖先) 路径压缩:(每 ...

  8. 最小生成树——Kruskal与Prim算法

    最小生成树——Kruskal与Prim算法 序: 首先: 啥是最小生成树??? 咳咳... 如图: 在一个有n个点的无向连通图中,选取n-1条边使得这个图变成一棵树.这就叫“生成树”.(如下图) 每个 ...

  9. [APIO2019] [LOJ 3145] 桥梁(分块+并查集)(有详细注释)

    [APIO2019] [LOJ 3145] 桥梁(分块+并查集)(有详细注释) 题面 略 分析 考试的时候就感觉子任务4是突破口,结果却写了个Kruskal重构树,然后一直想怎么在线用数据结构维护 实 ...

随机推荐

  1. gremlin语言语法--学习笔记

    学习gremlin语言的目的:测试图数据,支持gremlin语句,所以必须系统学习一下!!!! 一.基础查询 g.V() 查询所有的顶点 g.V(3) 查询顶点id为3的点.字符串id的要到引号V(& ...

  2. Zookeeper学习笔记(下)

    这是ZK学习笔记的下篇, 主要希望可以分享一些 ZK 的应用以及其应用原理 我本人的学习告一段落, 不过还遗留了一些ZK相关的任务开发和性能测试的任务, 留待以后完成之后再通过其他文章来进行分享了 Z ...

  3. ubuntu重装--备份/配置

    https://github.com/wenlin-gk/document/blob/master/ubuntu%E5%A4%87%E4%BB%BD%2B%E9%85%8D%E7%BD%AE.txt

  4. 对Nuxt的研究

    Nuxt就是基于Vue的一个应用框架,采用服务端渲染,让你的SPA应用(Vue)也可以拥有SEO Nuxt的生命周期有些在服务端(Node),客户端,甚至两边都在: 1.其他之前都不存在Window对 ...

  5. USACO Overplanting ( 线段树扫描线 )

    题意 : 在二维平面上给出 N 个矩形,问你所有矩形构成的图案的面积是多少(相互覆盖的地方只计算一次) 分析 :  求矩形面积并可以模拟来做,不过使用线段树来辅助做扫描线可以更高效地求解 扫描线顾名思 ...

  6. 放一道比较基础的LCA 的题目把 :CODEVS 2370 小机房的树

    题目描述 Description 小机房有棵焕狗种的树,树上有N个节点,节点标号为0到N-1,有两只虫子名叫飘狗和大吉狗,分居在两个不同的节点上.有一天,他们想爬到一个节点上去搞基,但是作为两只虫子, ...

  7. docker安装禅道

    一.下载地址 禅道开源版:   http://dl.cnezsoft.com/zentao/docker/docker_zentao.zip 数据库用户名: root,默认密码: 123456.运行时 ...

  8. 【bzoj3295】[Cqoi2011]动态逆序对

    题目描述: 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆 ...

  9. 普通用户sudo权限

    需求: 1>创建一个saipu普通用户,不允许使用 rm 和 passwd root 和 sudo su - root 命令,其他命令均允许且 sudo 时不用输入密码 2>创建一个lwd ...

  10. 关于c++ error : passing " "as" " discards qualifiers

    http://www.cppblog.com/cppblogs/archive/2012/09/06/189749.html 今天写了一段小代码,本以为正确,但运行后,就somehow ”discar ...