2024/11/3 随笔笔记
[NOIP2001 提高组] Car 的旅行路线
题目描述
又到暑假了,住在城市 A 的 Car 想和朋友一起去城市旅游。
她知道每个城市都有
4
4
4 个飞机场,分别位于一个矩形的
4
4
4 个顶点上,同一个城市中两个机场之间有一条笔直的高速铁路,第
i
i
i 个城市中高速铁路的单位里程价格为
T
i
T_i
Ti,任意两个不同城市的机场之间均有航线,所有航线单位里程的价格均为
t
t
t。
注意:图中并没有标出所有的铁路与航线。
那么 Car 应如何安排到城市 B 的路线才能尽可能的节省花费呢?她发现这并不是一个简单的问题,于是她来向你请教。
找出一条从城市 A 到 B 的旅游路线,出发和到达城市中的机场可以任意选取,要求总的花费最少。
输入格式
第一行为一个正整数 n n n,表示有 n n n 组测试数据。
每组的第一行有 4 4 4 个正整数 S , t , A , B S,t,A,B S,t,A,B。
S S S 表示城市的个数, t t t 表示飞机单位里程的价格, A A A, B B B 分别为城市A,B 的序号。
接下来有 S S S 行,其中第 i i i 行均有 7 7 7 个正整数 x i 1 , y i 1 , x i 2 , y i 2 , x i 3 , y i 3 , T i x_{i1},y_{i1},x_{i2},y_{i2},x_{i3},y_{i3},T_i xi1,yi1,xi2,yi2,xi3,yi3,Ti,这当中的 ( x i 1 , y i 1 ) , ( x i 2 , y i 2 ) , ( x i 3 , y i 3 ) (x_{i1},y_{i1}), (x_{i2},y_{i2}), (x_{i3},y_{i3}) (xi1,yi1),(xi2,yi2),(xi3,yi3),分别是第 i i i 个城市中任意 3 3 3 个机场的坐标, T i T_i Ti 为第 i i i 个城市高速铁路单位里程的价格。
输出格式
共有
n
n
n 行,每行
1
1
1 个数据对应测试数据。
保留一位小数。
样例 #1
样例输入 #1
1
3 10 1 3
1 1 1 3 3 1 30
2 5 7 4 5 2 1
8 6 8 8 11 6 3
样例输出 #1
47.5
提示
【数据范围】
对于
100
%
100\%
100% 的数据,
1
≤
n
≤
10
1\le n \le 10
1≤n≤10,
1
≤
S
≤
100
1\le S \le 100
1≤S≤100,
1
≤
A
,
B
≤
S
1\le A,B \le S
1≤A,B≤S。
【题目来源】
NOIP 2001 提高组第四题
思路:重点来了,一般来讲我们要分析数据范围,S<=100 ,在最短路的算法里面可以用单源最短路也可以用floyed这种O(n^3)的暴力算法求解,对于题目中的要求,我们必须要把同一座城市的四个机场坐标全部算出来,很明显,对于只给了三个点的城市坐标来说,我们可以通过勾股定理确定哪条边是斜边,哪条边是直边,进而求出第四个点的坐标,这里我给个推导过程。
我们用勾股定理确定斜边与直角边 a b 2 + b c 2 = c d 2 我们用勾股定理确定斜边与直角边 ab^{2} +bc^{2}=cd^{2} 我们用勾股定理确定斜边与直角边ab2+bc2=cd2
然后可以从已知的三个边中找出斜边,也就是说 c d 点的中点是与 a b 的中点重合那么就有 然后可以从已知的三个边中找出斜边,也就是说cd点的中点是与ab的中点重合 那么就有 然后可以从已知的三个边中找出斜边,也就是说cd点的中点是与ab的中点重合那么就有
{ a x + b x = c x + d x a y + b y = c y + d y {\Huge \begin{cases} a_{x} +b_{x}=c_{x}+d_{x} \\ a_{y}+b_{y}=c_{y}+d_{y} \end{cases}} ⎩ ⎨ ⎧ax+bx=cx+dxay+by=cy+dy
而且我们知道其中三个点的坐标那么我们就可以反推出第四个点的坐标
而且我们知道其中三个点的坐标那么我们就可以反推出第四个点的坐标
而且我们知道其中三个点的坐标那么我们就可以反推出第四个点的坐标
{
d
x
=
a
x
+
b
x
−
c
x
d
y
=
c
y
+
a
y
+
b
y
{\Huge \begin{cases} d_{x}=a_{x} +b_{x}-c_{x} \\ d_{y}=c_{y}+a_{y}+b_{y} \end{cases}}
⎩
⎨
⎧dx=ax+bx−cxdy=cy+ay+by
把所有的点的坐标算出来之后,我们就可以开始对dist这个数组开始进行处理了,首先我们要考虑是否可以直接用floyed求解,理论上来说是可以的,但是这里不推荐,因为如果直接进行floyed的话我们需要对中转点进行讨论,考虑到三个点需要进行是否在一个城市的讨论,这里涉及情况非常多,于是我们需要从两个点开始出发进行对数组的初始化,对于两个点来说,讨论他们的情况只有两种,要么这两个点在一个城市里,要不不在一个城市里,这两种情况通过坐标的存储技巧就可以简单化解。
最后就可以直接暴力的floyed,因为这个时候任意两个点之间都有直接到达的值,这样子floyed就无需再进行讨论了。
code
#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
typedef long long ll;
typedef double ld;
const int N=111;
int n;
int S,A,B;
ld t,T[N];
ld x[N<<3],y[N<<3];
ld dis[N<<3][N<<3];
ld ab,ac,bc;//求D点
ld ans;
ld dist(ld x1,ld y1,ld x2,ld y2)
{
return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
}
int main()
{
scanf("%d",&n);
while(n--)
{
ans=1e9;
memset(dis,0,sizeof(dis));
scanf("%d%lf%d%d",&S,&t,&A,&B);
for(int i=1;i<=S;++i)
{
for(int j=1;j<=3;++j)
scanf("%lf%lf",&x[((i-1)*4)+j],&y[((i-1)*4)+j]);
scanf("%lf",&T[i]);
}
for(int i=1;i<=S;++i)
{
ab=dist(x[(i-1)*4+1],y[(i-1)*4+1],x[(i-1)*4+2],y[(i-1)*4+2]);
bc=dist(x[(i-1)*4+2],y[(i-1)*4+2],x[(i-1)*4+3],y[(i-1)*4+3]);
ac=dist(x[(i-1)*4+1],y[(i-1)*4+1],x[(i-1)*4+3],y[(i-1)*4+3]);
//运用勾股找出中心点
if(ab+bc==ac)
x[i*4]=x[(i-1)*4+1]+x[(i-1)*4+3]-x[(i-1)*4+2],y[i*4]=y[(i-1)*4+1]+y[(i-1)*4+3]-y[(i-1)*4+2];
if(ab+ac==bc)
x[i*4]=x[(i-1)*4+2]+x[(i-1)*4+3]-x[(i-1)*4+1],y[i*4]=y[(i-1)*4+2]+y[(i-1)*4+3]-y[(i-1)*4+1];
if(ac+bc==ab)
x[i*4]=x[(i-1)*4+1]+x[(i-1)*4+2]-x[(i-1)*4+3],y[i*4]=y[(i-1)*4+1]+y[(i-1)*4+2]-y[(i-1)*4+3];
}
//初始化
for(int i=1;i<=(S<<2);++i)
for(int j=1;j<=(S<<2);++j)
{
if(i!=j)
{
if((i-1)/4==(j-1)/4)//说明在同一座城市
dis[i][j]=sqrt(dist(x[i],y[i],x[j],y[j]))*T[((i-1)/4)+1]*1.0;
else dis[i][j]=sqrt(dist(x[i],y[i],x[j],y[j]))*t*1.0;
}
}
for(int k=1;k<=(S<<2);++k)
for(int i=1;i<=(S<<2);++i)
for(int j=1;j<=(S<<2);++j) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
for(int i=1;i<=4;++i)
for(int j=1;j<=4;++j) ans=min(dis[(A-1)*4+i][(B-1)*4+j],ans);
printf("%.1f",ans);
}
return 0;
}