1. 程式人生 > >#10017. 「一本通 1.2 練習 4」傳送帶

#10017. 「一本通 1.2 練習 4」傳送帶

目錄

一、三分(絕大多數人第一個想到的)

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}​而且都成遞增或遞減。這樣就能證其為單峰函數了


對於一章的感想:

我曾經對二分三分充滿了信心,現在我對二分三分(尤其三分)充滿了絕望實在難以想象出題人是怎麼做到把一道二分三分題變成一道數論題,而且還是一道必須要用二分三分的數論題。經過一系列的二分三分折磨,我發現:線上段上面求極值的用二分,在一個平面上面求極值的就用三分,如果是多個平面求極值的話就用三分套三分。