środa, 21 sierpnia 2013

8981. Zamiana miejsc [WYMIANA]

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

Skrócony opis problemu:
Zamień wartości 2 zmiennych $x$ i $y$ bez korzystania z dodatkowych zmiennych.

Rozwiązanie:
Możemy użyć operatora XOR (w C '^').
$$x \verb!^!= y \verb!^!= x \verb!^!= y;$$
Dowodu nie chce mi się przytaczać, jako że znajduje się tu i jest wg mnie świetnie opisany.

Rozwiązanie nr 2:
Możemy też użyć dodawania i odejmowania.
$$x += y; \\ y = x-y; \\ x -= y;$$
Po pierwszej operacji w $x$ będzie suma $x+y$. Po drugiej operacji w $y$ będzie $suma-y=x$. Po trzeciej odejmujemy od sumy początkową wartość $x$, żeby otrzymać początkową wartość $y$.
Jako ciekawostka do kolejności wykonywania działań skrócony zapis:
$$x += y-(y=x);$$

Rozwiązanie nr 3:
Autorem jest aaa.
Przy pomocy wstawki asemblerowej.
scanf("%d %d", &x, &y);

asm "movl %1, %%eax;\n"
    "movl %2, %%ebx;\n"
    "movl %%ebx, %1;\n"
    "movl %%eax, %2;\n"
    : "=r"(x), "=r"(y)
    : "r"(x), "r"(y)
    : "eax", "ebx");

printf("%d %d", x, y);

Rozwiązanie nr 4:
Autorem jest Rafał Studnicki.
Kolejne przekształcenia arytmetyczne.
Wadą tego rozwiązania jest mnożenie przez 2. Dla większych liczb to rozwiązanie nie będzie działać, gdyż zostanie przekroczony typ int.
$$x += y; \\ y = x-2*y; \\ y = (x+y)/2; \\ x = x-y;$$

Rozwiązanie nr 5:
Autorem jest Mariusz Jakubowski.
Można by zrobić coś takiego:
$$x = y + 0 * (y=x);$$
...jednak kompilator to zoptymalizuje, usuwając niepotrzebny człon mnożony przez 0.
Można jednak zawoalować to trochę i zastąpić to 0 zdaniem logicznym:
$$x = (y + (x == y \ \&\& \ x != y)) * (1 + (y = x)*(x == y \ \&\& \ x != y));$$
Wadą tego rozwiązania jest to, że nie działa dla nowszych wersji Visual Studio (działa max do wersji 6.0).
Możemy jednak zrobić myk, korzystając z wiedzy, że dane nam są liczby naturalne:
$$x = (y + (x < 0)) * (1 + (y = x)*(x < 0));$$

Rozwiązanie z cheatem:
Nie zablokowałem używania frazy "define", bo jak ktoś wpadł na ten trick, to wykazał się wiedzą dot. define'ów.
Autor rozwiązania niech napisze w komentarzu nr jego zgłoszenia, bo nie pamiętam od kogo wziąłem ten kod.
scanf("%d %d", &x, &y);

#define ZAMIEN(a,b) ({in##t z = a; x = b; b = z;})
ZAMIEN(x,y);

printf("%d %d", x, y);

Rozwiązanie z cheatem nr 2:
Rozwiązanie opiera się na zapisaniu jednej zmiennej w drugiej. Najpierw przesuwamy bitowo w lewo jedną zmienną i doklejamy drugą na jej końcu. Następnie przypisujemy drugiej zmiennej pierwszą część pierwszej, a na końcu usuwamy początek z pierwszej zmiennej, tak aby został tylko koniec z początkową wartością drugiej.
Oznaczam to jako "cheat", jako że dla większych wartości zmiennych program nie przejdzie, gdyż 2 maksymalne wartości nie zmieszczą się w 1 zmiennej. Rozwiązanie to nie dostaje AC.
Autor rozwiązania niech napisze w komentarzu nr jego zgłoszenia, bo nie pamiętam od kogo wziąłem ten kod.
scanf("%d %d", &x, &y);

x = x << 10 | y;
y = (x & 0xffff0000) >> 10;
x = x & 0x0000ffff;

printf("%d %d", x, y);

Rozwiązanie z cheatem nr 3:
Autorem jest Przemek Komosa.
W tym rozwiązaniu zapisujemy wartość nie do zmiennej tymczasowej, ale pod pewien adres w pamięci. Logika taka sama.
Żeby to rozwiązanie nie przechodziło mógłbym zakazać znaku '&' poniżej scanfa, ale uznałem, że również jest to pomysłowe, więc przechodzi.
scanf("%d %d", &x, &y);

*(4 + &y) = x;
x = y;
y = *(4 + &y);

printf("%d %d", x, y);

Rozwiązanie z cheatem nr 4:
Autorem jest Przemek Komosa.
Cóż tu komentować. Pomysłowe. :-)
Aby z kolei tego uniknąć można było ustawić ilość returnów na 1.
int main()
{
 int x, y;
 scanf("%d %d", &x, &y);
 return display(y, x);
}

display(x, y)
{
 printf("%d %d", x, y);
 return 0;
}

Rozwiązanie z cheatem nr 5:
Autorem jest Michał Miodek.
Można jeszcze inaczej użyć define'a - bez dodatkowych '#'.
scanf("%d %d",&x,&y);

#define x y,x

printf("%d %d",x,y);

Rozwiązanie z cheatem nr 6:
Autorem jest Michał Miodek.
Można skorzystać ze znaku '\', który łączy daną linię z następną.
Rozwiązanie to nie przechodzi.
scanf("%d %d",&x,&y);

in\
t t=x;x=y;y=t;

printf("%d %d",x,y);

Jeśli macie inne rozwiązania tego zadania, to nie krępujcie się pisać w komentarzach!

6 komentarzy:

  1. można także tak:

    x = y + 0 * (y=x);

    ale aby kompilator nie zoptymalizował zapisujemy jako:

    x = (y + ((x==y)&&(x!=y))) * (1 + (y=x)*((x==y)&&(x!=y)));

    niewątpliwą wadą jest to, że używanie podstawienia po prawej stronie jest nieeleganckie (i kompilator zgłasza ostrzeżenia)
    oraz że nie działa dla nowszych wersji Visual Studio MS (ostatnia wersja dla której działa to 6.0)

    OdpowiedzUsuń
  2. Dzięki. :-) Dodałem myk, który być może sprawi, że w każdym Visualu kod zadziała. ;-)

    OdpowiedzUsuń
  3. Program, który przeszedł z klasyczną zamianą zmiennych:

    FILE f;
    f._flags2 = x;
    x = y;
    y = f._flags2;

    Próbowałem znaleźć jeszcze inne struktury zawierające pola typu int lub typedefy inne niż size_t ale nie udało mi się znaleźć nic kompilującego się w gcc (byłoby łatwiej jakbym miał je u siebie a nie na ideonie :)) Lokalnie Visual Studio działa mi jeszcze taki cheat:

    fpos_t t;
    t = x;
    ...

    OdpowiedzUsuń
  4. Dzięki za znalezienie kolejnego typu danych, który umożliwia obejście tego zadania! :-) Pewnie istnieją jeszcze jakieś inne typy, które przeoczyłem. Zmodyfikowałem treść zadania oraz wymieniłem masterjudge'a, ale nie rejudgnąłem Twoich submitów, bo należy Ci się AC dla tych kodów. Że też nie pomyślałem o polach złożonych typów danych... :P

    OdpowiedzUsuń
  5. Nie ma sprawy :) Ale niestety wciąż nie zablokowałeś wszystkiego. Właśnie wpadłem na to przy okazji innego zadanka:
    (*stdin)._flags2 = x;
    x = y;
    y = (*stdin)._flags2;

    stdin, stdout i stderr jest typu *FILE, aż się prosi żeby wyłuskać i ma się pola int tylko czekające żeby przechować zmienną ;)

    OdpowiedzUsuń
  6. Spryciarz. :-) A to niespodzianka, nie wpadłem na to. Zablokowałem również owe 3 nazwy. Dzięki!

    OdpowiedzUsuń