#10017. 「一本通 1.2 练习 4」传送带

版权声明:有女朋友的老江的博客,转载请告知老江 https://blog.csdn.net/qq_42367531/article/details/85009575

目录

一、三分(绝大多数人第一个想到的)

1、三分坐标直接求值

          2.三分比值

                 【代码实现】

二、模拟退火

三、暴力搜

           三分单峰函数证明

对于一章的感想:


【题目描述】

原题来自:SCOI 2010

在一个 2 维平面上有两条传送带,每一条传送带可以看成是一条线段。两条传送带分别为线段 AB 和线段 CD。lxhgww 在 AB 上的移动速度为 P ,在 CD 上的移动速度为 Q ,在平面上的移动速度 R。现在 lxhgww 想从 A 点走到 D 点,他想知道最少需要走多长时间。

【输入格式】

输入数据第一行是 4 个整数,表示 A 和 B 的坐标,分别为 Ax​,Ay​,Bx​,By​;

第二行是 4 个整数,表示 C 和 D 的坐标,分别为 Cx​,Cy​,Dx​,Dy​;

第三行是 3 个整数,分别是 P,Q,R。

【输出格式】

输出数据为一行,表示 lxhgww 从 A 点走到 D 点的最短时间,保留到小数点后 2 位。

【样例输入】

0 0 0 100
100 0 100 100
2 2 1

【样例输出】

136.60

【数据范围与提示】

对于 100% 的数据,1≤Ax​,Ay​,Bx​,By​,Cx​,Cy​,Dx​,Dy​≤1000,1≤P,Q,R≤10。


 

这道题一看到显然我是知道可以用三分的,但是发现有了三分之后就有点小难过,因为三分之后就没有了思路,所以感谢大佬的博客(建议先看完大佬的博客再来细节了解)。

这道题有好几个大思路,好几个小思路

一、三分(绝大多数人第一个想到的)

1、三分坐标直接求值

这是最常见的思路了,很多大佬都是用三分坐标的,因为很好理解。

我们进行三分套三分,把线段ab三分寻找转折点后从转折点跑向线段cd,然后再在线段cd上三分寻找抵达地点跑向点d。(参考)【大佬代码】


2.三分比值

这个是我重点要讲的,也是我觉得最方便最好解释的方法。

  上网看了大佬的博客,发现可以三分比值。具体如下:

我们现在已知线段AB,假设现在在AB上已找到一点F,我们要去计算F与另一条线段的距离,同时这个F点其实就是在AB上的一个转折点,这个F的坐标怎么求呢?

AB为斜边,作一个Rt\Delta ABE。作FH\perp AE,此时我们可以发现,\Delta AFH\Delta ABE是相似三角形(\angle A为公共角,\angle FHA=\angle E=90^{\circ}

所以AFAB有一定的比值,即\frac{AF}{AB}=k(0\leq k\leq 1)(AF无论如何都不应该比AB要大)

所以我们可以直接三分k,然后就可以求出F的坐标了。通过AB来求出AF。

同样的方法三分线段CD,用三分套三分(同三分坐标的方法)。

我们找到的这个CD上的点就是与AB上的F点相连接的点。使得这个距离可以最短。

【代码实现】

#include<cmath>
#include<cstdio>
#include<cstring>
using namespace std;
struct node
{
	double x,y;//x左边和y左边 
}a,b,c,d; double p,q,r;
double gougu(node n1,node n2)//勾股求斜边的长度 
{
	return sqrt((n1.x-n2.x)*(n1.x-n2.x)+(n1.y-n2.y)*(n1.y-n2.y));
	//(两个点相对应的横左边相减的平方+两个点相对应的纵坐标相减的平方)再开方
	//这一步不难理解主要的目的是为了求出AF的长度 
}
node find(node n1,node n2,double k)
{
	node no; no.x=(n2.x-n1.x)*k+n1.x; no.y=(n2.y-n1.y)*k+n1.y;//找出F点的左边
	//横左标就是AB的长度乘以比值就是AF的长度,横坐标就是加上A点的横坐标 
	//纵左标就是AB的长度乘以比值就是AF的长度,纵坐标就是加上A点的纵坐标
	/*
	可能会有疑问就是说,知道长度就好了为什么还要求左边?
	因为我们知道了坐标之后,才能带入坐标求出长度啊,所以这就是为什么我们要用
	三分比值来找出这个坐标的原因 
	*/ 
	return no;
}
double checkjuli(double x,double y)
{
	node n1=find(a,b,x),n2=find(c,d,y);//定义两点,目的是为了算出定值然后求出坐标 
	return gougu(a,n1)/p+gougu(n1,n2)/r+gougu(n2,d)/q;
	//gougu(a,n1)/p 表示AB上的点到A的长度
	//gougu(n1,n2)/r 表示AB上的点到CD上的点的长度 
	//gougu(n2,d)/q 表示CD上的点到D的长度 
}
double check(double x)
{
	double l=0.0,r=1.0;
	while(r-l>=1e-7)
	{
		double mid1=l+(r-l)/3.0,mid2=r-(r-l)/3.0;
		if(checkjuli(x,mid1)>checkjuli(x,mid2)) l=mid1;
		//如果我们代入的这个点在mid1的长度>在mid2的长度说明这不是上升序列
		//说明我们找到的不是最标准的最小值的上升序列 
		else r=mid2;
	}
	return checkjuli(x,l);
}
int main()
{
	scanf("%lf%lf%lf%lf",&a.x,&a.y,&b.x,&b.y);
	scanf("%lf%lf%lf%lf",&c.x,&c.y,&d.x,&d.y);
	scanf("%lf%lf%lf",&p,&q,&r);
	double l=0.0,r=1.0;
	while(r-l>=1e-7)
	{
		double mid1=l+(r-l)/3.0,mid2=r-(r-l)/3.0;
		if(check(mid1)>check(mid2))l=mid1;
		//这一步就是所谓的三分套三分,因为我们是先找到一个点到CD上距离最短
		//然后再找一个点是CD到AB上最短的点
		//然后这两个点的距离+各自到节点的距离就会使得A点到D点的距离最短 
		else r=mid2;
	}
	printf("%.2lf",check(l));//把上升序列的l再走一遍流程算出最后的长度 
	return 0;
}

这一步就是三分比值的做法,我觉得是相对来讲没有那么复杂,也是好理解一点的,其实再转过头看一下,和三分坐标的做法有几分相似,共同点:通过在两条线段上面的转折点来找到最小值,只是方法不一样性质是一样的。 


二、模拟退火

不说别的,我也不会,直接看优秀博客


三、暴力搜

两位小数,的确也可以暴力找。也有大佬是先暴力在AB上找点,然后再三分CD的,会慢一点,但是也可以过,而且保险很多。


三分单峰函数证明

要知道为什么可以用三分才行。

首先,我们画一个图。假设上面的点为线段AB上的某一点,它到线段CD的所有路径中,选出了a和b两条,设a为最优解。我们可以画出以下图片:

而b肯定是比a要长的,所以我们可以再画出下面这个:

我们可以看出,b路径和a路径唯一的区别就是在于b−a这一段和c这一段。我们不妨求出它们的时间:

b=\frac{a}{r}+\frac{b-a}{r}+\frac{c}{q}     (假设我们要让b和c同时到达CD上,线上的距离的速度是一样的,那就只能使得再c这一段的速度快一点才有可能追上a)

a=\frac{a}{r}

一般来说,只有c这段路程是走得比b快的,即q> r,b有可能在c超车,我们要判断是否存在这种情况,即\frac{b-a}{r}<\frac{c}{q}​,b就可以超车,反之亦然。

所以我们可以找到一条路径,使其左边的都为\frac{b-a}{r}<\frac{c}{q}且右边的都为\frac{b-a}{r}>\frac{c}{q}​而且都成递增或递减。这样就能证其为单峰函数了


对于一章的感想:

我曾经对二分三分充满了信心,现在我对二分三分(尤其三分)充满了绝望实在难以想象出题人是怎么做到把一道二分三分题变成一道数论题,而且还是一道必须要用二分三分的数论题。经过一系列的二分三分折磨,我发现:在线段上面求极值的用二分,在一个平面上面求极值的就用三分,如果是多个平面求极值的话就用三分套三分。

猜你喜欢

转载自blog.csdn.net/qq_42367531/article/details/85009575