pamiętnik programisty

01 sty, 2010

HSSPOJ – zadanie które nie dawało mi spokoju

Zamieszczony przez: pejotr w: Algorytmika| Programowanie| Python

Zadanie z konkursu algorytmicznego dla uczniów szkół średnich. Zwróciłem na nie uwagę, gdyż współczynnik poprawnych rozwiązań wynosił lekko ponad 30%, a skoro to konkurs dla licealistów to powinienem sobie dać radę. I tu pierwszy szok, nic z tego… Wiedza jaką dysponowałem okazała się daleko nie wystarczająca, a próby znalezienia rozwiązania podanego na tacy spełzły na niczym. Co dodatkowo rozbudziło moją ciekawość to fakt że na forach poświęconych programowaniu, nikt nie szuka pomocy.
Zadanie finalnie okazało się rozbudowaną grą Nim, ale bez znajomości jednego twierdzenia teorii gier prawdopodobnie siedziałbym na tym jeszcze dłuuuugo…
Treść zadania dostępna pod adresem http://hs.spoj.pl/problems/HS09GAME/

Wspomniane twierdzenie to Twierdzenie Sprague-Grundy’ego. Gra przestawiona w zadaniu spełnia wszystkie założenia pozwalające na skorzystanie z twierdzenia. W połączeniu z dokładnie omówioną optymalną strategią, opisem pozycji wygrywających i przegrywających w grze Nim, pierwszą część zadania można rozwiązać w zaledwie 9 liniach

# gry - lista skladajaca sie z list reprezentujacych gre. Kazda reprezentacja
# gry to kolejna lista zaiwerajaca ilosc 'kijkow' na kolejnych stosach
def graj(gry):
    sg = (0, 0, 1, 1, 2, 2, 3)
    for gra in gry:
        g = []
        reszty = []
        for stos in gra:
            reszta = int(stos)%7
            g.append(sg[reszta])
            reszty.append(reszta)
        wynik = reduce(lambda x,y: x^y, g)

        if wynik :
            print "Julia wins."
        else :
            print "Robert wins.\n"

Lekki problem pojawia się przy ustalaniu ruchu wygrywającego. Nie wiem czy istnieje jakieś szybsze rozwiązanie niż takie trochę brute-force’owe która ja wykorzystałem. Polega ono na stwierdzeniu na jakiej pozycji ( wygrywającej czy przegrywającej ) znalazł by się gracz gdyby nie było jednego stosiku, a następnie obliczeniu czy da się odjąć taką liczbę patyczków żeby zostawić przeciwnika na pozycji przegrywającej.
Dodatkowo oprócz wskazania ruchu wygrywającego należy wskazać ten podczas którego bierze się najwięcej patyczków ze stosu o najmniejszym numerze, co w lekki sposób komplikuje sprawę.:

        stos_wygrywajacy = 0;
        licznik = 0
        blokada_5 = False
        blokada_3 = False

        # odwrocenie list, sprawdzanie od konca
        gra_rev = gra[:]
        gra_rev.reverse()

        g_rev = g[:]
        g_rev.reverse()

        reszty.reverse()
        for reszta in reszty:
            # lista reszt bez kolejnych stosow
            tmpr = reszty[:]
            tmpr.pop(licznik)

            # xor wartosci f. Sprague-Grundy'ego
            # bez kolejnych stosow
            tmpg = g_rev[:]
            tmpg.pop(licznik)
            tmpw = reduce(lambda x,y: x^y, tmpg ,0)

            if sg[reszta-5] == tmpw and int(gra_rev[licznik]) >= 5:
                wygrywajacy = 5
                stos_wygrywajacy = len(reszty) - licznik
                blokada_5 = True
            elif sg[reszta-3] == tmpw and int(gra_rev[licznik]) >= 3 and blokada_5 == False:
                wygrywajacy = 3
                stos_wygrywajacy = len(reszty) - licznik
                blokada_3 = True
            elif sg[reszta-2] == tmpw and int(gra_rev[licznik]) >= 2 and blokada_5 == False and blokada_3 == False:
                wygrywajacy = 2
                stos_wygrywajacy = len(reszty) - licznik

            licznik += 1

Jeśli ktoś zna szybsze rozwiązanie chętnie je przeanalizuję :)
Zaznaczam, że nie dam sobie głowy uciąć że algorytm jest poprawny, bo nie ma już możliwości zgłoszenia rozwiązania – upłynęły terminy.

EDIT:
Lekko poprawiona wersja, działa trochę szybciej, natomiast i tak nie umożliwia zdobycia maximum punktów. Jesli nie ma jakiejś sztuczki na ruch wygrywający, a podejrzewam że coś jest, to lepiej zakodzić to w C…

        stos_wygrywajacy = 0
        blokada_3 = False
        blokada_2 = False
        licznik = 0
        for reszta in reszty :
            tmpg = g[:]
            tmpg.pop(licznik)
            tmpw = reduce(lambda x,y: x^y, tmpg, 0)

            if sg[reszta-5] == tmpw and int(gra[licznik]) >= 5:
                wygrywajacy = 5
                stos_wygrywajacy = licznik + 1
                break
            elif sg[reszta-3] == tmpw and int(gra[licznik]) >= 3 and blokada_3 == False:
                wygrywajacy = 3
                stos_wygrywajacy = licznik + 1
                blokada_3 = True
            elif sg[reszta-2] == tmpw and int(gra[licznik]) >= 2 and blokada_3 == False and blokada_2 == False:
                wygrywajacy = 2
                stos_wygrywajacy = licznik + 1
                blokada_2 = True
            licznik += 1

6 odpowiedzi na "HSSPOJ – zadanie które nie dawało mi spokoju"

1 | kuszi

1. Styczeń 2010 o 22:40

Avatar

Sympatyczny post, dzięki! Jedna uwaga: zadania można zgłaszać po terminie tu: http://www.spoj.pl/HSPLARCH/

2 | pejotr

2. Styczeń 2010 o 11:41

Avatar

o super, dzięki za info.
Dostałem 6pkt, ale przynajmniej poprawnie rozwiązane :) – czas wykonania 0,17 i użyta pamięć 3,6MB.
Nie wiem jakie są dokładnie zasady oceniania, ale chyba obcięli za pamięć. Jakiś zawodnik od C++ miał czas wykonania 0,19 a zużycie pamięci 2,7 – dostał 10pkt

3 | pejotr

2. Styczeń 2010 o 14:28

Avatar

Jak by sie lepiej poszukalo, to nie trzeba by bylo tyle myslec, ale bylo warto:
http://marcin-skiba.blogspot.com/2009/10/strategy-game-opracowanie-zadania_24.html :)

4 | kuszi

2. Styczeń 2010 o 16:40

Avatar

Klikając na “6″ w kolumnie result swojego zgłoszenia powinieneś widzieć szczegóły oceny dla poszczególnych testów (jest TLE dla dwóch ostatnich).

“.. a skoro to konkurs dla licealistów to powinienem sobie dać radę.” – w tej serii pewnie też cos ciekawego się znajdzie. Te zadania robią nie tylko licealiści (np: triplem

5 | pejotr

2. Styczeń 2010 o 17:54

Avatar

Faktycznie “Przekroczony limit czasu” (3 krotnie) dla 2 ostatnich testów :)
Jeszcze raz dzięki za oświecenie :)

6 | kuszi

19. Styczeń 2010 o 00:21

Avatar

Rozwiązania wzorcowe nie są (jak do tej pory) publikowane, więc takie wpisy mogą interesować uczestników – w przyszłości zapraszam do dzielenia się również na forum konkursu.

Formularz komentarza

O mnie:

pejotrWitam, nazywam się Piotr Doniec, w internecie występuję pod nickami 'pejotr' oraz 'doniczek'. Obecnie jestem studentem 3 roku informatyki na Politechnice Warszawskiej na wydziale Elektroniki i Technik Informacyjnych.

Kategorie