Processing math: 1%

piątek, 30 sierpnia 2013

10348. Taksówka na Manhattanie 4 [TAXIMAN4]

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

Skrócony opis problemu:
Dla danych n (n \le 10^5) punktów z przestrzeni d-wymiarowej (mających d < 17 współrzędnych) wypisać odległość w metryce Manhattan między dwoma najdalszymi punktami.


Rozwiązanie naiwne:
Dla każdej pary punktów obliczyć odległość między nimi i wybrać największą z nich.

Złożoność: O(n^2 \cdot d), jako że par punktów jest \frac{n(n-1)}{2}, a odległość między każdą parą obliczamy w czasie \Theta(d) (przechodząc wszystkie d współrzędnych).

Rozwiązanie sprytniejsze:
Najpierw opiszę rozwiązanie dla d=2 a potem je uogólnię.

Chcemy znaleźć najdalszą parę punktów w 2D. Określmy odległość każdego punktu od (0;0). Jest to |y_i|+|x_i|. Mając 2 punkty możemy obliczyć odległość między nimi za pomocą ich odległości od (0;0). Np. dla (1;2) i (6;7) wartości to odpowiednio 3 i 13. Odległość między nimi to zatem |3-13|=10. ALE dla punktów (1;7), (6;-1) to nie działa. Dlaczego? Bo dla pierwszej pary odcinek wygląda jak / a dla drugiej jak \ . W pierwszym przypadku jest dobrze, bo odległość między pierwszym punktem i (0;0) ma wspólny początek z odległością między drugim punktem i (0;0), a w drugim przypadku niekoniecznie. Jak możemy to naprawić? Obracając układ współrzędnych albo, konkretniej, oś OX, zamieniając znaki wszystkim x. Dla punktów (1;7), (6;-1) obliczamy zatem dwie wartości: y_i+x_i (8 dla pierwszego punktu i 5 dla drugiego) oraz y_i-x_i (6 dla pierwszego punktu i -7 dla drugiego). Z ich pomocą możemy obliczyć ich odległość - jest to większa z |8-5|=3 i |6+7|=13, a więc 13. Możemy więc znaleźć min i max dla y_i+x_i oraz min2 i max2 dla y_i-x_i w czasie liniowym. Co z tym robimy? Porównujemy |max-min| oraz |max2-min2| i wybieramy większe z nich. Jest to nasz wynik (największa odległość między dwoma punktami w 2D).

Możemy to teraz uogólnić dla d > 2. Na początku, nie znajdujemy jedynie y_i+x_i i y_i-x_i, ale wszystkie kombinacje znaków (znaku + i znaku -). Na przykład dla: 1 2 3 4 musimy sprawdzić sumy: 1+2+3+4, 1+2+3-4, 1+2-3+4, 1+2-3-4, 1-2+3+4, 1-2+3-4, 1-2-3+4 i 1-2-3-4. Dlaczego nie -1+2+3+4? Bo jest to ciąg 1-2-3-4 z przeciwnym znakiem (a interesuje nas wartość bezwzględna odległości). Tak więc trzymamy maxy i miny dla każdej takiej kombinacji znaków. Będzie 2^{d-1} maxów i 2^{d-1} minów. Po wczytaniu wszystkich punktów po prostu znajdujemy maksymalną wartość |max_i-min_i| dla i \in \left<0;2^{d-1}\right> (indeksując od 0).
Warto tutaj zaznaczyć, że sprawdzamy wszystkie kombinacje znaków +, - poruszając się w systemie binarnym. Tak więc np. dla d=4 zaczynamy od 0000, 0001, 0010, itd. 0 na danej pozycji oznacza znak +, a 1 znak - (albo odwrotnie - jest to obojętne, bo i tak bierzemy moduł, czyli wartość bezwzględną, wyniku).

Złożoność: O(n \cdot d \cdot 2^d), bo dla każdego z n punktów obliczamy każdą z 2^{d-1} kombinacji znaków w czasie stałym (możemy zamienić 0000 na 0001 dodając do inta 1) i obliczamy jej sumę (dodając do wyniku przy 0 i odejmując przy 1, a na końcu biorąc moduł) w czasie \Theta(d).

Algorytm:
int main()
{
    int n, d, i, j, k, m, coord[d];
    long long x, min[m], max[m];
    
    get(d, n);
    m = 1<<(d-1); // kombinacji będzie 2^(d-1)
    for(i = 0; i < m; ++i) // inicjalizacja minów i maxów
        min[i] = 1e15,
        max[i] = -1e15;
    while(n--) // dla każdego punktu
    {
        for(j = 0; j < d; ++j) // pobierz jego współrzędne
            get(coord[j]);
        for(j = 0; j < m; ++j) // dla każdej kombinacji
        {
            x = 0;
            for(k = 0; k < d; ++k) // oblicz sumę opierając się na reprezentacji binarnej kolejnych intów i dodając jeśli na jakiejś pozycji jest 1 i odejmując dla 0 (albo odwrotnie)
                x += coord[k] * (j&(1<<k) ? 1 : -1);
            if(x < min[j]) // aktualizuj min dla danej kombinacji
                min[j] = x;
            if(x > max[j]) // aktualizuj max dla danej kombinacji
                max[j] = x;
        }
    }
    x = -1;
    for(i = 0; i < m; ++i) // weź maxa z modułów max-min
        if(llabs(max[i]-min[i]) > x)
            x = llabs(max[i]-min[i]);
    print(x);
}

Rozwiązanie jeszcze sprytniejsze:
Możemy zmodyfikować powyższy algorytm obliczając sumy kolejnych permutacji w czasie stałym.
Możemy zauważyć, że możemy obliczyć początkową sumę w czasie \Theta(d), a kolejne moglibyśmy obliczać w czasie stałym, gdyby... zmieniała się tylko jedna pozycja. Możemy to zagwarantować korzystając z kodu Graya zamiast kodu binarnego. Kod Graya ma bowiem to do siebie, że kolejny wyraz różni się od poprzedniego dokładnie jednym bitem. Ale jak przejść z jednej "liczby Graya" na następną? Ano za pomocą tego wzoru:
\text{Gray}(x) = x \oplus \left\lfloor\frac{x}{2}\right\rfloor
...który oblicza x-tą liczbę Graya (\oplus oznacza operację XOR). Ale jak dostać pozycję, która uległa zmianie? Ano możemy obliczyć \text{Gray}(x) \oplus \text{Gray}(x+1) (która zwróci nam potęgę 2, a więc w reprezentacji binarnej będziemy mieli ...000001000...) i w czasie O(d) znaleźć pozycję tej jedynej jedynki. Ale będziemy mieli taką samą złożoność, jak ostatnio! Tak, dla każdej z n liczb mamy nadal 2^{d-1} kombinacji znaków + i - i dla każdej obliczamy sumę w czasie stałym, ale przechodzimy teraz z jednej na drugą w czasie O(d). Możemy jednak to zauważyć, że te wszystkie pozycje muszą być obliczone tylko raz. Ergo, możemy obliczyć dla każdej liczby z przedziału \left<1;2^{d-1}\right) (kombinacji znaków) w czasie O(d), a później sprawdzić ją w czasie stałym (dla 0 nie obliczamy, bo nie ma od niej wcześniejszej liczby Graya, z której pozycja by się zmieniła). Musimy też pamiętać czy na danej pozycji 0 zmieniło się w 1 czy na odwrót. Dla zamiany 1 na 0 ja będę po prostu dawał znak '-' przed liczbą.

Złożoność: O(d \cdot 2^d + n \cdot (d + 2^d) + 2^d) \approx O(n \cdot 2^d). Maksymalne d jest tak małe, że jeśli nie było w wykładniku, to uznałem je za stałą.

Algorytm:
int main()
{
    int n, d, i, j, k, m, a, b, coord[d], pos[m];
    long long x, min[m], max[m];
    
    get(d, n);
    m = 1<<(d-1); // 2^(d-1)
    for(i = 0; i < m; ++i) // preprocessing; obliczanie wszystkich pozycji, na których zmieniają się bity liczby Graya
    {
        min[i] = 1e15;
        max[i] = -1e15;
        if(i)
        {
            pos[i] = 1; a = i^(i>>1); b = (i-1)^((i-1)>>1); k = a^b; // wzory na n-tą i (n-1)-tą liczbę Graya, a następnie ich XOR
            while(k%2 == 0) // dla potęgi 2 oblicza jej wykładnik w pesymistycznym czasie O(d)
                ++pos[i],
                k >>= 1;
            if(b < a) // zrób liczbę ujemną jeśli bit 1 zmienił się w 0
                pos[i] = -pos[i];
        }
    }
    while(n--)
    {
        for(j = 0; j < d; ++j)
            get(coord[j]);
        for(x = k = 0; k < d; ++k) // oblicz pierwszą sumę
            x += coord[k];
        if(x < min[0])
            min[0] = x;
        if(x > max[0])
            max[0] = x;
        for(j = 1; j < m; ++j) // sprawdź wszystkie kombinacje znaków
        {
            x += 2L*coord[llabs(pos[j])-1]*(pos[j] > 0 ? 1L : -1L); // dodaj lub odejmij jeden element (2 razy, bo jeśli wcześniej był na + to żeby był na minusie to musimy go odjąć 2 razy)
            if(x < min[j])
                min[j] = x;
            if(x > max[j])
                max[j] = x;
        }
    }
    for(x = -1, i = 0; i < m; ++i)
        if(llabs(max[i]-min[i]) > x)
            x = llabs(max[i]-min[i]);
    print(x);
}

Optymalizacja: Można również stablicować wszystkie potęgi 2. Dzięki temu będziemy otrzymywać wykładni w czasie stałym, zamiast w O(d). Złożoność asymptotyczna będzie jednak taka sama.

Rozwiązanie najsprytniejsze:
Rozwiązanie to to nic innego jak połączenie pierwszego i trzeciego algorytmu w zależności od wartości n i d.
Jeśli n^2 \cdot d < n \cdot 2^d to korzystamy z pierwszego algorytmu, w przeciwnym razie z trzeciego.

Uwaga: Aby zaliczyć to zadanie warto zamienić funkcję llabs na własną funkcję obliczającą moduł liczby.

Brak komentarzy:

Prześlij komentarz