środa, 12 listopada 2014

629. Skąpiec [MISER]

Zadanie:
https://pl.spoj.com/problems/MISER

Skrócony opis problemu:
Dla danego zbioru $n$ punktów w przestrzeni 2-wymiarowej należy wyznaczyć dowolne 2 punkty, takie że okrąg utworzony na nich oraz na pierwszym punkcie z wejścia (nazwijmy go $A$) nie zawiera w sobie żadnego innego punktu (mogą jednak się znaleźć punkty na samym okręgu). Żadne 3 punkty nie są współliniowe.

Rozwiązanie naiwne:
Możemy oczywiście sprawdzić każdą parę punktów dodając do niej $A$. Dla każdej takiej trójki można sprawdzić każdy punkt czy przypadkiem nie leży w jej wnętrzu. Jeśli jakiś leży, to sprawdzamy kolejną parę.

Złożoność: $O\left(n^3\right)$, bo mamy dokładnie $\frac{(n-1)(n-2)}{2}$ par punktów jeśli pominiemy $A$, którego i tak musimy uwzględnić. A dla każdej z tych trójek musimy sprawdzić pesymistycznie każdy punkt czy nie leży on wewnątrz owego okręgu.

Rozwiązanie sprytniejsze:
Jako że interesuje nas dowolna trójka punktów spełniająca wspomniany warunek, możemy od razu wybrać punkt najbliższy $A$. Im bliżej są te 3 punkty, tym mniejsze jest koło, a więc tym większe jest prawdopodobieństwo, że nic w nim nie będzie. Czujny czytelnik zauważy, że poprzednie zdanie jest fałszywe. Możemy mieć bowiem punkty prawie współliniowe, czyli np. $A = (0, 0), B = (-50, 1), C = (50, 1)$. Powiedzmy, że $B$ i $C$ są najbliżej $A$. Jednak okrąg, który te punkty tworzą jest ogromny. Tak więc nie możemy zachłannie wybierać 2 najbliższych punktów. Po co o tym wspomniałem? Ano dlatego, że możemy jednak wykorzystać jeden najbliższy punkt i dobrać ostatni sprawdzając każdą możliwość.

Tak więc szukamy punktu leżącego najbliżej pierwszego, a następnie jak w poprzednich algorytmie, tworzymy trójkę punktów dla każdego z pozostałych $n-2$ punktów i znów liniowo sprawdzamy czy któryś z $n-3$ punktów nie leży w środku owego okręgu.

Złożoność: $O\left(n^2\right)$, gdyż na początku w $\Theta(n)$ szukamy najbliższego punktu do $A$, następnie bierzemy każdy z pozostałych $n-2$ punktów i dla każdej z takich $n-2$ trójek sprawdzamy maksymalnie $n-3$ punktów.

Rozwiązanie najsprytniejsze:
Z poprzedniego rozwiązania mamy już 2 punkty $A$ i $B$. Został nam tylko punkt $C$. Jak go znaleźć szybciej niż w $O\left(n^2\right)$? Ano możemy być zachłanni. Ale przecież napisałeś, że jak wybierzemy 2 najbliższe punkty do $A$ to nie uzyskamy najmniejszego koła! To prawda, ale możemy wziąć inne kryterium niż odległość od $A$. Co może być owym kryterium? Ano chcemy uzyskać jak najmniejsze koło, więc po prostu dla każdego punktu obliczymy promień okręgu powstałego z utworzonej trójki punktów i wybierzemy taki punkt $C$, dla którego ów promień będzie najmniejszy. Tyle. Promień ten możemy obliczyć korzystając z poniższych wzorów:
$$R = \frac{a \cdot b \cdot c}{4 \cdot P}\\
P = \sqrt{p \cdot (p-a) \cdot (p-b) \cdot (p-c)}\\
p = \frac{a+b+c}{2}$$
$R$ to właśnie promień okręgu opisanego na trójkącie, którego wierzchołkami są $A$, $B$, $C$. $a$, $b$, $c$ to długości boków tego trójkąta. $P$ to jego pole obliczone z wzoru Herona. $p$ jest natomiast połową jego obwodu.

Złożoność: $\Theta(n)$, bo na początku znajdujemy punkt najbliższy $A$ przeglądając wszystkie $n-1$ punktów, a potem znajdujemy punkt, który tworzy najmniejszy promień przeglądając $n-2$ punktów.

Algorytm:
int main()
{
        int n, i, j, k, x[10009], y[10009];
        long double a, b, c, area, p, R, min = 9e18;

        get(n, x[0], y[0]);
        for(i = 1; i < n; ++i)
        {
                get(x[i], y[i]);
                if((x[i]-x[0])*(x[i]-x[0])+(y[i]-y[0])*(y[i]-y[0]) < min)
                        min = (x[i]-x[0])*(x[i]-x[0])+(y[i]-y[0])*(y[i]-y[0]),
                        j = i;
        }
        min = 9e18;
        a = (x[0]-x[j])*(x[0]-x[j])+(y[0]-y[j])*(y[0]-y[j]);
        for(i = 1; i < n; ++i)
        {
                if(i == j)
                        continue;
                b = (x[0]-x[i])*(x[0]-x[i])+(y[0]-y[i])*(y[0]-y[i]);
                c = (x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);
                R = b*c/(4*a*b-(a+b-c)*(a+b-c));
                if(R < min)
                        min = R,
                        k = i;
        }
        print(1, j+1, k+1);
}

Brak komentarzy:

Prześlij komentarz