#10017. 「一本通 1.2 練習 4」傳送帶
阿新 • • 發佈:2018-12-15
目錄
【題目描述】
原題來自: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、三分座標直接求值
這是最常見的思路了,很多大佬都是用三分座標的,因為很好理解。
我們進行三分套三分,把線段三分尋找轉折點後從轉折點跑向線段 ,然後再線上段上三分尋找抵達地點跑向點。(參考)【大佬程式碼】
2.三分比值
這個是我重點要講的,也是我覺得最方便最好解釋的方法。
上網看了大佬的部落格,發現可以三分比值。具體如下:
我們現在已知線段,假設現在在上已找到一點,我們要去計算與另一條線段的距離,同時這個F點其實就是在AB上的一個轉折點,這個的座標怎麼求呢?
以為斜邊,作一個。作,此時我們可以發現,和是相似三角形(為公共角,)
所以和有一定的比值,即(AF無論如何都不應該比AB要大)
所以我們可以直接三分,然後就可以求出的座標了。通過AB來求出AF。
同樣的方法三分線段,用三分套三分(同三分座標的方法)。
我們找到的這個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和c同時到達CD上,線上的距離的速度是一樣的,那就只能使得再c這一段的速度快一點才有可能追上a)
一般來說,只有c這段路程是走得比b快的,即,b有可能在c超車,我們要判斷是否存在這種情況,即,b就可以超車,反之亦然。
所以我們可以找到一條路徑,使其左邊的都為且右邊的都為而且都成遞增或遞減。這樣就能證其為單峰函數了
對於一章的感想:
我曾經對二分三分充滿了信心,現在我對二分三分(尤其三分)充滿了絕望。實在難以想象出題人是怎麼做到把一道二分三分題變成一道數論題,而且還是一道必須要用二分三分的數論題。經過一系列的二分三分折磨,我發現:線上段上面求極值的用二分,在一個平面上面求極值的就用三分,如果是多個平面求極值的話就用三分套三分。