1. 程式人生 > 實用技巧 >圓的國度:Can you understand what you see?

圓的國度:Can you understand what you see?

題目描述:

平面上有 n 個沒有公共點 的圓。你要從點(x1,y1)走到(x2,y2)。問你最少要經過多少圓的邊界。保證這兩個點都不在圓的邊界上。

輸入格式:

問題輸入: 第一行一個整數 n, 1<=n<=50。 接下來三行每行 n 個整數,分別表示 n 個圓的圓心和半徑,格式如下: x1,x2……xi……xn , y1,y2……yi……yn , r1,r2……ri……rn -1000<=xi,yi<=1000,1<=ri<=1000 。最後一行四個整數 x1,y1,x2,y2, -1000<=x1,y1,x2,y2<=1000。

輸出格式:

問題輸出: 一個整數,意義如上。

樣例:

輸入: 輸入:

3 1

0 -6 6 0

0 1 6 0

2 2 2 2

-5 1 5 1 -5 1 5 1

輸出:

1 0

luogu連結:https://www.luogu.com.cn/problem/T139630



相信大家看到這道題的第一印象都是:

好 ~~一 ~~道 ~~水~~題 ~~

迴圈輸入這些玩意,把起點終點一連,寫個計數器,搞幾個判斷不就完了嗎,這題不是有手就行???

然而,事實真的如此嗎?

根據題意,我們很容易畫出樣例圖:

(樣例一)(請原諒我拙劣的畫圖技術)

WTF?!?!?!?!

這明明經過了2個圓的邊界,為啥子樣例輸出是1???

難道樣例有問題???

樣例二也有相同的問題,在圖上穿過了一個一個圓,而輸出卻是0。

經驗告訴我們樣例錯誤的情況極少出現,更何況一次錯兩個。出問題的一定是我們自己。


我們不妨重新審視一下題目。

注意【你要從點(x1,y1)走到(x2,y2)】,題目中僅僅說走到,卻絲毫未提及怎麼走。難道道路只有連線兩點的一條嗎?顯然並非如此。題目中沒有說必須走直線,翻譯過來就是:只要能到,你怎麼蛇皮走位都沒問題。

於是,樣例一便可以這麼走:

甚至這樣走:

這樣都滿足了從一點到另一點的要求,而且只穿過一個圓,符合樣例輸出。

結合上圖可知,與穿過幾個圓直接掛鉤的是起點和終點是否在圓內,若在,則會穿過一圓

而點是否在圓內,只需要比較點到圓心的距離與該圓半徑,若距離>半徑,則點在圓外,若距離<半徑,則點在圓內(資料保證點不在圓上)。

故我們可以寫出程式:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
int n,x[60],y[60],r[60];
int s1,s2,s3,x01,y01,x02,y02;
int main()
{
    scanf("%d",&n);
    int count=0;
    for(int i=1;i<=n;i++)  //注意輸入圓心和半徑要分開輸入
    {
        scanf("%d",&x[i]);
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&y[i]);
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&r[i]);
    }
    scanf("%d%d%d%d",&x01,&y01,&x02,&y02);
    for(int i=1;i<=n;i++)
    {
        s1=sqrt((x01-x[i])*(x01-x[i])+(y01-y[i])*(y01-y[i]));  //計算點到圓心距離
        s2=sqrt((x02-x[i])*(x02-x[i])+(y02-y[i])*(y02-y[i]));
        if(s1<r[i])  count++;  //判斷點到圓心距離和半徑哪個大
        if(s2<r[i])  count++; 
    }
    printf("%d",count);
    return 0;
}

這段程式看上去沒什麼問題,兩個樣例也都能過,那就交吧!

然而…………

WA~~~~~~~~~~~~~~~~~~~~

最後一個測試點沒有過,說明還有問題。

遇到只錯了一個測試點的情況,我們該怎麼辦呢?

當然是卡測試點繼續debug。

我們上上方的程式中判斷計數是否加一,是通過比較點到圓心距離和圓的半徑,但是這種操作並不適用於一種較特殊的情況,如圖:

很明顯,這種情況下從A到B需要穿過的圓的數目為0,但若以我們的評測標準,因為A和B到圓心距離都小於圓的半徑,計數會加2,所以得出的答案是2,明顯錯誤。這就是兩點在同一圓內的特殊情況,需要另加判斷。

我們很容易發現,在這種情況下,兩個點到圓心的距離都小於半徑,而之前的情況是一個點到圓心距離小於半徑、一個大於半徑,於是,我們便可以由此寫出特判程式:

if(s1<r[i]&&s2<r[i])  continue;  //使用continue直接跳過此情況。注意,要先判斷是否在同一圓內,否則計數會先加2
else if(s1<r[i]||s2<r[i])  count++;   

到此這道題就全部結束了,AC程式碼:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
int n,x[60],y[60],r[60];
int s1,s2,s3,x01,y01,x02,y02;
int main()
{
    scanf("%d",&n);
    int count=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&x[i]);
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&y[i]);
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&r[i]);
    }
    scanf("%d%d%d%d",&x01,&y01,&x02,&y02);
    for(int i=1;i<=n;i++)
    {
        s1=sqrt((x01-x[i])*(x01-x[i])+(y01-y[i])*(y01-y[i]));
        s2=sqrt((x02-x[i])*(x02-x[i])+(y02-y[i])*(y02-y[i]));
        if(s1<r[i]&&s2<r[i])  continue;
        else if(s1<r[i]||s2<r[i])  count++; 
    }
    printf("%d",count);
    return 0;
}

小蒟蒻的此篇題解就到此結束了,喜歡的話還請留下一個大拇指。