• Nie Znaleziono Wyników

Pochodzenie: Rozwi¸azanie:

XII Olimpiada Informatyczna commando.cpp

Na Pustyni Błędowskiej odbywa się w tym roku Bardzo Interesuj¸aca i Widowiskowa Akcja Komandosów (BIWAK). Podstawowym elementem BIWAK-u ma być neutralizacja bomby, która znajduje się gdzieś na pustyni, jednak nie wiadomo dokładnie gdzie.

Pierwsza część akcji to desant z powietrza. Z helikoptera kr¸aż¸acego nad pustyni¸a, wyskakuj¸a pojedynczo, w ustalonej kolejności komandosi. Gdy któryś z komandosów wyl¸aduje w jakimś miejscu, okopuje się i już się nie rusza z miejsca. Dopiero potem może wyskoczyć kolejny komandos.

Dla każdego komandosa określona jest pewna odległość rażenia. Jeśli komandos przebywa w tej odległości (lub mniejszej) od bomby, to w przypadku jej ewentualnej eksplozji zginie.

Dowództwo chce zminimalizować liczbę komandosów bior¸acych udział w akcji, ale chce mieć pewność, że w przypadku wybuchu bomby, przynajmniej jeden z komandosów przeżyje.

Na potrzeby zadania przyjmujemy, że Pustynia Błędowska jest płaszczyzn¸a, a koman-dosów, którzy się okopali utożsamiamy z punktami. Mamy dany ci¸ag kolejno mog¸acych wyskoczyć komandosów. Żaden z nich nie może opuścić swojej kolejki, tzn. jeśli i-ty ko-mandos wyskakuje z samolotu, to wszyscy poprzedni wyskoczyli już wcześniej. Dla każdego z komandosów znamy jego odległość zagrożenia rażenia oraz współrzędne punktu, w którym wyl¸aduje, o ile w ogóle wyskoczy.

Zadanie

Napisz program, który:

ˆ wczyta ze standardowego wejścia opisy komandosów,

ˆ wyznaczy minimaln¸a liczbę komandosów, którzy musz¸a wyskoczyć,

ˆ wypisze wynik na standardowe wyjście.

Wejście

W pierwszym wierszu standardowego wejścia zapisana jest jedna liczba całkowita n (2 ¬ n ¬ 2 000) — liczba komandosów. W kolejnych n wierszach opisani s¸a komandosi — po jednym w wierszu. Opis każdego komandosa składa się z trzech liczb całkowitych: x, y i r (−1 000 ¬ x, y ¬ 1 000, 1 ¬ r ¬ 5 000). Punkt (x, y) to miejsce, gdzie wyl¸aduje komandos, a r to jego odległość „rażenia”. Jeśli komandos znajdzie się w odległości r lub mniejszej od bomby, to w przypadku jej wybuchu zginie.

Wyjście

W pierwszym i jedynym wierszu standardowego wyjścia Twój program powinien zapisać jedn¸a liczbę całkowit¸a - minimaln¸a liczbę komandosów, którzy musz¸a wyskoczyć, aby zapewnić, że co najmniej jeden z nich przeżyje, lub jedno słowo NIE jeśli nie jest możliwe, aby mieć pewność, że któryś z komandosów przeżyje.

Przykład

Dla następuj¸acego wejścia:

5 2 2 4 7 2 3 4 3 1 5 7 1 8 7 1

Poprawnym rozwi¸azaniem jest:

4

Ćwiczenia

Proste Średnie Trudne

acm.uva.es - zadanie 191 acm.sgu.ru - zadanie 253 spoj.sphere.pl - zadanie 182 acm.sgu.ru - zadanie 124 spoj.sphere.pl - zadanie 102 spoj.sphere.pl - zadanie 332 acm.sgu.ru - zadanie 129 acm.sgu.ru - zadanie 198 spoj.sphere.pl - zadanie 272 acm.uva.es - zadanie 10084 acm.sgu.ru - zadanie 267

X Y

(-3, 0)

(2, 5)

(2, -5) (2, 0)

Rysunek 3.8: Rysunek prezentuj¸acy okręgi wyznaczone przez zbiór czterech punktów. S¸a tylko trzy, a nie cztery okręgi opisane na tych punktach, ponieważ punkty (2, −5), (2, 0) oraz (2, 5) s¸a współliniowe i nie wyznaczaj¸a okręgu

3.5. Trzy punkty — okr¸ag

W poprzednich rozdziałach przedstawiliśmy wiele różnych funkcji operuj¸acych na okręgach.

Przyjmowaliśmy wtedy, że okr¸ag reprezentowany jest w postaci pary (środek okręgu, promień).

Nie jest to jednak jedyny sposób — w wielu przypadkach okręgi reprezentuje się jako trzy różne punkty należ¸ace do ich obwodu. W sytuacjach, w których konieczne jest korzystanie z różnych reprezentacji okręgu (co może być wymuszone formatem danych wejściowych w zada-niu), pojawia się potrzeba konwertowania jednej reprezentacji do innej. Konwersja reprezen-tacji okręgu ((C.x, C.y), r) używanej w funkcjach z tej ksi¸ażki do reprezenreprezen-tacji wykorzys-tuj¸acej trzy punkty jest prosta. Jako reprezentantów okręgu można użyć przykładowo następu-j¸acy zbiór punktów: {(C.x, C.y + r), (C.x, C.y − r), (C.x + r, C.y)}. Konwersja w drug¸a stronę wydaje się być znacznie bardziej skomplikowana — nad ni¸a właśnie skupimy się w aktualnym rozdziale.

Załóżmy zatem, że mamy dane trzy punkty: P1, P2oraz P3, które nie leż¸a na jednej prostej.

W celu wyznaczenia środka oraz promienia okręgu opisanego na tych trzech punktach, należy rozwi¸azać układ równań z trzema niewiadomymi r, C.x oraz C.y postaci:

r2= (C.x − P1.x)2+ (C.y − P1.y)2 r2= (C.x − P2.x)2+ (C.y − P2.y)2 r2= (C.x − P3.x)2+ (C.y − P3.y)2

Prezentowana na listingu 3.37 funkcja służ¸aca do konwertowania reprezentacji okręgu nie implementuje rozwi¸azywania powyższego układu równań wprost, lecz wykorzystuje przed-stawion¸a wcześniej funkcję do wyznaczania punktu przecięcia dwóch prostych. Przyjmuje ona jako parametry trzy punkty oraz referencję na punkt C i zmienn¸a r typu double. Jeśli na trzech określonych przez parametry punktach można opisać okr¸ag, funkcja zwraca prawdę, punktowi C przypisywany jest środek, a zmiennej r promień wyznaczonego okręgu.

Listing 3.37: Implementacja funkcji bool ThreePointCircle(POINTD, POINTD, POINTD, POINTD&, double&)

// Funkcja znajduje okrąg wyznaczony przez trzy punkty lub zwraca fałsz, // jeśli taki okrąg nie istnieje

01 bool ThreePointCircle(POINTD p1, POINTD p2, POINTD p3, POINTD &c, double &r){

// Wyznacz punkt przecięcia symetralnych trójkąta (p1, p2, p3)

Listing 3.37: (c.d. listingu z poprzedniej strony)

02 if (LineCrossPoint(POINTD((p1.x + p2.x) / 2.0, (p1.y + p2.y) / 2.0), 03 POINTD((p1.x + p2.x) / 2.0 + p2.y - p1.y, (p1.y + p2.y) / 2.0 + p1.x 04 - p2.x), POINTD((p1.x + p3.x) / 2.0, (p1.y + p3.y) / 2.0),

05 POINTD((p1.x + p3.x) / 2.0 + p3.y - p1.y, 06 (p1.y + p3.y) / 2.0 + p1.x - p3.x) ,c) != 1) 07 return false;

// Wylicz promień okręgu o środku w punkcie c 08 r = sqrt(sqr(p1.x - c.x) + sqr(p1.y - c.y));

09 return true;

10}

Listing 3.38: Przykład użycia funkcji bool ThreePointCircle(POINTD, POINTD, POINTD, POINTD&, double&)na przykładzie zbioru punktów przedstawionych na rysunku 3.8

Punkty: (-3, 0), (2, 0), (2, 5)

Srodek okregu = (-0.5, 2.5), promien = 3.53553 Punkty: (2, -5), (2, 0), (2, 5)

Punkty sa wspolliniowe

Punkty: (2, -5), (-3, 0), (2, 5)

Srodek okregu = (2, 0), promien = 5 Punkty: (2, -5), (-3, 0), (2, 0)

Srodek okregu = (-0.5, -2.5), promien = 3.53553

Listing 3.39: Kod źródłowy programu użytego do wyznaczenia wyniku z listingu 3.38. Pełny kod źródłowy programu znajduje się w pliku threepointcircle.cpp

01 intmain() {

02 vector<POINT> l;

03 POINT p;

04 POINTD po;

05 double r;

06 bool res;

// Wczytaj listę punktów

07 while (cin >> p.x >> p.y) l.PB(p);

// Dla każdej trójki punktów, wyznacz okrąg na nich opisany 08 REP(x, SIZE(l)) REP(y, x) REP(z, y) {

09 cout << "Punkty: " << l[x] << ", " << l[y] << ", " << l[z] << endl;

10 if (!ThreePointCircle(l[x], l[y], l[z], po, r))

11 cout << "Punkty sa wspolliniowe" << endl;

12 else cout << "Srodek okregu = " << po << ", promien = " << r << endl;

13 }

14 return 0;

15}

3.6. Sortowanie k¸atowe

Sortowanie k¸atowe zbioru punktów S dookoła wektora C 7→ D polega na takim uporz¸adkowa-niu punktów, że dla dwóch dowolnych punktów P1 oraz P2 ze zbioru S, P1 znajduje się przed punktem P2, jeśli k¸aty skierowane6 DCP1 oraz6 DCP2zachowuj¸a własność6 DCP1 <

6 DCP2. W przypadku, gdy6 DCP1=6 DCP2, umawiamy się, że jako wcześniejszy wybierany jest punkt znajduj¸acy się bliżej punktu C.

Sortowanie k¸atowe można zrealizować przy użyciu funkcji sortpochodz¸acej z biblioteki STL. Jedyne co trzeba zrobić, to dostarczyć operator, który dla dwóch danych punktów będzie w stanie stwierdzić, który z nich jest mniejszy. Ze względu jednak na konieczność rozpatrzenia wielu przypadków, implementacja takiego operatora jest stosunkowo skomplikowana. Poza tym, funkcja sort nie przekazuje żadnej informacji na temat wektora, względem którego wykonywane jest sortowanie i trzeba by było operatorowi porównuj¸acemu punkty dostarczyć informację na temat tego wektora.

Prezentowana w tym rozdziale funkcja również wykorzystuje funkcjęsort, jednak wcześniej dokonuje podziału zbioru punktów na dwie części — punkty znajduj¸ace się po lewej stronie prostej C → D oraz resztę. Dzięki wprowadzeniu takiego podziału, znika wiele przypadków skrajnych, które normalnie należałoby rozpatrzyć, a w rezultacie implementacja operatora wykorzystywanego przez funkcję sort jest prosta. Po posortowaniu obu zbiorów punktów wystarczy je z powrotem scalić. Złożoność czasowa sortowania, ze względu na wykorzystanie funkcjisortto O(n∗log(n)). Rysunek 3.9 prezentuje proces wyznaczania porz¸adku k¸atowego dla przykładowego zbioru punktów.

Prezentowana na listingu 3.40 funkcja vector<POINT*> AngleSort(vector<POINT>&

p, POINT s, POINT k)przyjmuje jako parametry listę punktów do posortowania oraz dwa punkty C i D. Jako wynik działania zwracany jest wektor wskaźników na punkty w kolejności zgodnej z porz¸adkiem k¸atowym.

Listing 3.40: Implementacja funkcji vector<POINT*> AngleSort(vector<POINT>&, POINT, POINT)

// Wskaźnik na punkt centralny (używane przez funkcję porównującą) 01POINT* RSK;

// Funkcja porównująca punkty 02 bool Rcmp(POINT *a, POINT *b) { 03 LL w = Det((*RSK), (*a), (*b));

04 if (w == 0) return abs(RSK->x - a->x) + abs(RSK->y - a->y) <

05 abs(RSK->x - b->x) + abs(RSK->y - b->y);

06 returnw > 0;

07}

// Funkcja sortuje po kącie odchylenia zbiór punktów względem punktu centralnego // s zaczynając od wektora s -> k

08vector<POINT*> AngleSort(vector<POINT>& p, POINT s, POINT k) { 09 RSK = &s;

10 vector<POINT*> l, r;

// Każdy punkt, który podlega sortowaniu, zostaje wstawiony do jednego // z wektorów l lub r, w zależności od tego,

// czy znajduje się po lewej czy po prawej stronie prostej s -> k 11 FOREACH(it, p) {

12 LL d = Det(s, k, (*it));

13 (d > 0 || (d==0 && (s.x == it->x ? s.y < it->y : s.x < it->x))) 14 ?l.PB(&*it) : r.PB(&*it);

X Y

(-7, 5) (-3, 4)

(-6, -6) (-1, -1)

(2, 6)

(4, 0) (7, 0)

(8, 7)

(a)

X Y

(b)

X Y

(c)

Rysunek 3.9: (a) Zbiór punktów do posortowania k¸atowego względem wektora (0, 0) 7→ (1, 3). (b) Rozdzielenie zbioru punktów na dwa podzbiory i posortowanie punktów w ich obrębie.

(c) Scalenie obu zbiorów

Listing 3.40: (c.d. listingu z poprzedniej strony) 15 }

// Posortuj niezależnie punkty w obu wyznaczonych wektorach 16 sort(ALL(l), Rcmp);

17 sort(ALL(r), Rcmp);

// Wstaw wszystkie punkty z wektora r na koniec wektora l 18 FOREACH(it, r) l.PB(*it);

19 return l;

20}

Listing 3.41: Przykład działania funkcji vector<POINT*> AngleSort(vector<POINT>&, POINT, POINT)dla przykładowego zbioru punktów z rysunku 3.9

(2, 6) (-3, 4) (-7, 5) (-1, -1) (-6, -6) (4, 0) (7, 0) (8, 7)

Listing 3.42: Kod źródłowy programu użytego do wyznaczenia wyniku z listingu 3.41. Pełny kod źródłowy programu znajduje się w pliku anglesort.cpp

01 intmain() { 02 POINT c, k;

03 int n;

// Wczytaj liczbę punktów oraz współrzędne wektora, względem którego wykonywane // będzie sortowanie

04 cin >> n >> c.x >> c.y >> k.x >> k.y;

05 vector<POINT> l(n);

// Wczytaj wszystkie punkty

06 REP(x, n) cin >> l[x].x >> l[x].y;

// Posortuj punkty

07 vector<POINT *> res = AngleSort(l, c, k);

08 FOREACH(it, res) cout << " " << *(*it);

09 return 0;

10}