Zadanie 1

alt text

Ze wzoru Eulera wiemy, że !!\sum_{i=1}^n \frac{1}{i} \approx \log n!!, as !!n!! approaches infinity.

!! T(n) = 2T(\frac{n}{2}) + \frac{n}{\log n} = !!
!! = 2(2*T(\frac{n}{4}) + \frac{n/2}{\log \frac{n}{2}}) + \frac{n}{\log n} =!!
!! = 4T(\frac{n}{4}) + \frac{n}{\log \frac{n}{2}} + \frac{n}{\log n} =!!
!! = n \cdot 1 + \sum_{i=1}^{\log n} \frac{n}{\log 2^i} = !!
!! = n + n \sum_{i=1}^{\log n} \frac{1}{\log 2^i} = !!
!! = n + n \sum_{i=1}^{\log n} \frac{1}{i} = (euler)!!
!! = n + n \log\log(n) \approx !!
!! \approx n \log\log(n) !!

Zadanie 2

alt text

Disclaimer: w moim rozwiązaniu przechodzimy przez piekło dla strywializowania problemu (a przynajmniej piekło dla mnie, bo nie miałem rozwa :(). Co więcej, nie wiem czy przejście przez piekło nie jest grzybable.

Oczywiście zaczynamy od próby zrozumienia o co chodzi.

alt text

Kolorkami zaznaczyłem zbiory punktów (!!q!!) na trzech prostych, które są widoczne z p, ten jasnozielonkawy to !!x = 0!!. Zatem te 3 proste są widoczne z p. Teraz, w zadaniu musimy znaleźć wszystkie proste które są widoczne z punktu !!(0, +\infty)!!.

Na co musimy zwrócić uwagę?
Proste z największym i najmniejszym współczynnikiem kierunkowym na pewno będą widoczne. Teraz najważniejsza rozkmina - jakie proste jeszcze będą widoczne? Posortujmy po b. Na pewno prosta z największym będzie widoczna. Mamy takie szczypce z prostych ze skrajnymi współczynnikami kierunkowymi i prostą przechodzącą przez nie. Teraz, żeby kolejna prosta mogła być widoczna, to jej punkt przecięcia z tą prostą z największym b musi być w środku "szczypiec".

Już tłumaczę. Umieśćmy nasze proste jako punkty w układzie współrzędnym, zaznaczmy sobie te skrajne i ten z największym b. Tzn, dla prostej (y = ax + b) wstawiamy na układ współrzędnych punkt (a, b). Przeprowadźmy przez nie proste.

alt text

Każdy punkt odpowiada prostej z zadania - tzn. są to punkty dla prostych !!y = -15x - 3!!, !!y = -3x + 9!!, !!y = 15x - 2!!. Tak się one mają na wykresie (niebieski i czerwony to "szczypce"): alt text

Rozważmy dodatkową prostą, np. !!y = 2x + 2!!.

alt text alt text

Jak widzimy, przecinają się poza "szczypcami", nie są widoczne. Rozważmy teraz prostą !!y = 4x + 5!!.

alt text alt text

Ledwo bo ledwo, ale przecinają się w "szczypcach", będzie widoczna.

Teraz ważna obserwacja - jak wyrazić proste, które łączą np. !!(-3, 9)!! i !!(15, -2)!!? Oczywiście obliczamy sobie prostą przechodzącą przez dwa punkty, będzie to !!y = \frac{11}{18} x + 7.1666667!!. Wyliczone współczynniki tej prostej są niczym innym jak ich punktem przecięcia. To znaczy, proste !!y = -3x + 9!! i !!y = 15x - 2!! przecinają się w punkcie !!P(\frac{11}{18}, 7.166667)!!. No i teraz nasz nowy punkt leży "nad" tą prostą, prosta będzie przechodziła pomiędzy prostymi !!(-3, 9)!! i !!(15, -2)!! nad ich punktem przecięcia, więc będzie widoczna. Troche lipnie wytłumaczone, ale nie umiem lepiej. Prosta !!(4, 5)!! będzie widoczna i updatujemy nasze proste.

alt text

Teraz, jeżeli jakaś prosta będzie pod tym pomarańczowym wykresem, to będzie się przecinała z innymi prostymi pod widocznymi prostymi, więc nie będzie widoczna. W tym momencie dochodzimy do tego o co chodziło mi na samym początku. Wykminienie i pokazanie (jak ktoś umie to pokazać dokładniej i łatwiej to błagam, powiedzcie mi) o co tu chodzi to big brain, ale samo rozwiązanie jest absolutnie trywialne. Wystarczy przedstawić nasze proste jako punkty i znaleźć ich górną otoczkę wypukłą.

Do znalezienia otoczki górnej wykorzystamy monotone chain convex hull algorithm, algos z wiki.

def find_upper_convex_hull(points):
    points = sorted(set(points))

    if len(points) <= 1:
        return points

    def cross(o, a, b):
        return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])

    upper = []
    for p in reversed(points):
        while len(upper) >= 2 and cross(upper[-2], upper[-1], p) <= 0:
            upper.pop()
        upper.append(p)

    return upper


def find_visible_lines(lines):
    points = map(lambda x: (x.a, x.b), lines)
    return find_upper_convex_hull(lines)

Zadanie 3

alt text

!!k = 2!!

Dzielimy se nasz argument na pół, jak w Karatsubie. Mamy !!x = x_0 \cdot 2^\frac{n}{2} + x_1!!. Po skwadraceniu, dostajemy: !!x^2 = x_0^2 \cdot 2^n + 2 \cdot x_0 x_1 \cdot 2^\frac{n}{2} + x_1^2!!

No i teraz obserwacja: !!2 x_0 x_1 = (x_0 + x_1)^2 - x_0^2 - x_1^2!!.

Zatem !!x_0^2, x_1^2!! obliczamy rekurencyjnie, !!(x_0 + x_1)^2!! też. Mamy więc nadal 3 wywołana rekurencyjne wartości które są ~połowę mniejsze, złożoność będzie taka sama.

!!k = 3!!

Chyba chodzi o Toom-Cook. Na początku myślałem że tu złożoność będzie gorsza, także lepiej nie ufać temu podpunktowi z k = 2 też.

Zadanie 6

alt text

def DFS(curr, acc, C, res):
    if acc <= C:
        # dla każdego nieodwiezdonego sąsiada curr
        for each unvisited v: e(curr, v):
            res += DFS(v, acc + weight(curr, v), C, res)
            if (acc == C):
                res += 1
            return res
    else:
        return 0

def pairs(T, C):
    if (C == 0):
        return n

    res = 0
    for each v in T:
        res += DFS(v, 0)
    res /= 2

    return res

Dla każdego wierzchołka przechodzimy do maksymalnie !!n!! innych wierzchołków, zatem złożoność to !!O(n^2)!!.

Zadanie 8

alt text alt text

Przykład jak tego nie robić - dostałem za to 0 na egzaminie :(

Przesunięcie cykliczne - np. mając 11101101 i przesuwając to cyklicznie o 3, dostaniemy 01101111, tj. 3 pierwsze lądują na początku, a pozostałe są przesunięte. Może lepiej będzie widać jak przesuniemy cyklicznie o 3 ciąg abcdefgh, będzie to wtedy defghabc.

Zacznijmy od przetworzenia naszych danych. Zauważmy, że np. dwa przesunięcia o 1 są równoważne jednemu przesunięciu o 2, dwa przesunięcia o 2 są równoważne jednemu przesunięciu o 4 i tak dalej. Doprowadzimy dane do postaci, gdzie będziemy mieć maks jedno przesunięcie danego rodzaju. Posortujmy nasz !!P_n!!. Dla każdych dwóch !!2^i!!, usuwamy dwa !!2^i!! ze zbioru i dodajemy jedno !!2^{i+1}!!. Mamy zbiór z przesunięciami, gdzie jest maksymalnie jedno !!2^i!! na zbiór. Przesunięcia większe bądź równe !!n!! możemy po prostu wyrzucić, jako że przesunięcie ciągu n-elementowego o n zwróci nam ten sam ciąg, a przesunięcie ciągu n-elementowego o jeszcze większą potęgę dwójki tylko powtórzy operację przesunięcia o n ileś razy. Dla prostoty, nasz zbiór możemy zwyczajnie przedstawić teraz jako !!\sqrt n !!-bitową liczbę, gdzie !!i!!-ty bit będzie mówił, czy potrzebne jest przesunięcie o !!2^i!!.

Przykład konstrukcji sieci:
alt text

Krok pierwszy, mamy 1 - zatem wymagane jest przesunięcie o 8.

alt text

Przesunięcia o 4 nie ma, przespiujemy.

alt text

Przesunięcie o 2 jest, to przesuwamy o 2.

alt text

Przesunięcie o 1 też jest, no to też przesuwamy.

alt text

I mamy nasze wyjście. Ogólniej mówiąc, budując sieć, na danym kroku, jeżeli !!P_n[i] = 1!!, to każdy element przesuwamy w lewo o !!2^i !!, bierzemy to modulo !!2^n!!, czyli elementy które przsuwane w lewo wysuwają sie za zakres, pojawiają się na początku.