Rozwiązanie prostej zagadki na początek 2024 roku

krótki URL: /393s

kategorie:Jestem, więc myślę, Serie
tagi:None

Zagadkę da się sposób rozpykać za pomocą brute-force:

def ZnajdźProstopadłościan(przekątna):
    NajwiększaObjętość = 0
    Wymiary = (0, 0, 0) # (a, b, c)

    for a in range(1, przekątna):
        for b in range(a, przekątna):  # Zaczynamy od a żeby uniknąć zduplikowanych par
            for c in range(b, przekątna):  # Zaczynamy od b z tego samego powodu
                if a**2 + b**2 + c**2 == przekątna**2:
                    Objętość = a * b * c
                    if Objętość > NajwiększaObjętość:
                        NajwiększaObjętość = Objętość
                        Wymiary = (a, b, c)

    return Wymiary, NajwiększaObjętość

przekątna = 2024
Wymiary, Objętość = ZnajdźProstopadłościan(przekątna)

print(Wymiary, Objętość)

Powyższy kod wykonuje się na moim wysłużonym i7 dobrze ponad 6 minut - i nie dziwota, bo nawet po eliminacji duplikatów nadal mamy coś z miliard wykonań pętli wewnętrznej, z sześcioma mnożeniami, dwoma dodawaniami i Wogle. Ale po zakończeniu wypluwa on:

(1104, 1104, 1288) 1569835008

Mamy więc odpowiedź do pierwszego pytania.

Zauważamy przy okazji, że nasz prostopadłościan jest prawie sześcianem. Sześcian z taką przekątną miałby długość boku około 1168.56 i objętość około 1595706627 - a więc nasze rozwiązanie jest "odległe" od sześcianu zaledwie o 1.6%.

Drugie pytanie było przewrotne ponieważ dotyczyło dokładnie tego samego problemu, tylko w drugą stronę: szukamy prostopadłościanu o przekątnej 2024 i krawędziach całkowitych dodatnich, ale z najmniejszą możliwą objętością. Zamieniając znak większości na mniejszości w powyższym kodzie (oraz startując z objętością od p3 zamiast od zera) dostaniemy następujący wynik:

(64, 168, 2016) 21676032

I to jest poprawna odpowiedź na pytanie bonusowe. Natomiast jeżeli chodzi o zadanie bonusowe na sterydach, czyli szukanie najpojemniejszego prostopadłościanu "na piechotę" to temat robi się zdecydowanie ciekawszy.

Zaczniemy od prostej matematyki. Z Pitagorasa mamy: $$a^2+b^2+c^2=p^2$$ Skoro p=2024, to szukamy trójki liczb (a, b, c) takich, że suma ich kwadratów wynosi 4096576. Pamiętajmy jednak, że mamy do dyspozycji wyłącznie kartkę i ołówek. Szukanie takiej trójki może nam więc trochę zająć...

Ale, ale. Przekątna ma wymiar liniowy, podobnie jak krawędź. A więc skrócenie przekątnej o połowę powinno nam też zmniejszyć długość każdej krawędzi o połowę. Prawda?

Sprawdźmy: $$\left(\frac{a}{2}\right)^2+\left(\frac{b}{2}\right)^2+\left(\frac{c}{2}\right)^2=\left(\frac{p}{2}\right)^2$$ Mnożąc obie strony przez 4 dostajemy to samo co na początku, czyli wszystko się zgadza. Zamiast dwójki możemy oczywiście użyć (prawie) dowolnej innej liczby.

No dobra. To już coś.

2024 dzieli się przez 8, 11 i 23 (co łatwo sprawdzić na piechotę). A więc jeżeli przeskalujemy przekątną w dół do 11 (czyli podzielimy początkowe 2024 przez 8 i potem jeszcze przez 23) i znajdziemy rozwiązanie dla takiego małego prostopadłościanu, to potem wystarczy przeskalować wynik w drugą stronę et voila!

112=121[citation needed]

Szukamy więc trzech liczb a, b, c takich, że: $$a^2+b^2+c^2=121$$, pamiętając jednocześnie, że: $$0 < a <= b <= c < 11$$

Kombinacji jest w dalszym ciągu parę setek, ale po pierwsze parę setek to nie miliardy, a po drugie zanim zaczniemy jak jakaś porąbana mróweczka sprawdzać kolejne kombinacje, zauważamy jeszcze coś. Otóż wiadomo, że optymalne rozwiązanie - o ile istnieje - będzie kształtem w miarę możliwości zbliżone do sześcianu. Jedna trzecia ze 121 to pi x drzwi 40, a więc warto zacząć od kwadratów, których wartości lądują w pobliżu 40. 62=36 (blisko), 72=49 (też blisko), więc sprawdzamy na dzień dobry szóstki i siódemki. I faktycznie: $$6^2+6^2+7^2=36+36+49=121$$ W końcu skalujemy ten wynik w górę: 6823=1104, 7823=1288 - noż kurdę, zgadza się!

PostScriptum: powyższa metoda działa tylko dla niektórych przekątnych, o czym poinformował mnie Waldek (patrz komentarze). Nie ma róży bez kolców, czy coś...

Zanim jednak założymy na głowę zwycięski laur zwycięstwa... znaczy, tego, laur... no dobra, zanim więc zaczniemy pląsać z radości w blasku księżyca, odpowiedzmy sobie jeszcze na jedno, ale to za*ście ważne pytanie.

A mianowicie: dlaczego zredukowaliśmy przekątną z 2024 akurat do 11, a nie, dajmy na to, do 2? Przecież skoro 2024 dzieli się przez 1012, to zamiast kombinować z 112 moglibyśmy równie dobrze kombinować z 22, prawda? Mniejsza liczba, mniej kombinowania. No więc jak to jest?

A no jest tak, że 4 jest po prostu za małe, żeby je przedstawić w postaci sumy trzech kwadratów. Nie da się i już.

Jeżeli szukalibyśmy rozwiązania uniwersalnego (tj. dla każdej możliwej przekątnej a nie tylko 2024), to po rozkładzie długości przekątnej na czynniki pierwsze musimy wybrać najmniejszy z nich, ale różny od 2 lub 5 - a następnie znaleźć dlań sumę trzech kwadratów (może być siłowo).

A czemu różny od 5?

Tu w sukurs przychodzi nam twierdzenie Legendre'a o sumie trzech kwadratów, które mówi, że liczba naturalna może być przedstawiona w postaci sumy kwadratów trzech liczb naturalnych wtedy i tylko wtedy, jeżeli nie jest ona postaci 4k(8m+7). Dla przekątnej p=5 mamy 52=25=40(8*2+7), czyli dupa. Natomiast wszystkie liczby pierwsze większe od 5 podniesione do kwadratu nie dają się przedstawić w postaci 8m+7 (dowód pozostawiam Czytelnikowi - trzeba wystartować od faktu, że dowolna liczba pierwsza większa od 3 jest postaci 6k±1), więc dla wszystkich pozostałych liczb pierwszych p da się znaleźć co najmniej jedną trójkę (a, b, c) taką, że suma ich kwadratów daje p2, co zresztą sprawdziłem eksperymentalnie Pythonem aż do p=10000 - zgadza się.

from sympy import primerange

def find_positive_squares_for_prime(prime):
    target = prime ** 2
    for x in range(1, prime + 1):
        for y in range(x, prime + 1):
            for z in range(y, prime + 1):
                if x ** 2 + y ** 2 + z ** 2 == target:
                    return (x, y, z)
    return None

for p in primerange(2, 10000):
    print(p, find_positive_squares_for_prime(p))

Ostrzegam lojalnie: duuuuużo czekania, bo czym większe p, tym dłużej się liczy.

A jak Wam poszło?

1Jako pierwszy odezwał się tacitgreg, którego odpowiedź na pytanie główne jest poprawna, bonusowe - niepoprawna, a zadanie "na piechotę" w ogóle nie ruszone. Nie zaliczam (acz strona www imponująca!)

2Drugim rozwiązującym był Greg. Bez adresu, więc nie wiem czy to ten sam Greg co przedtem, czy jakiś inny. Odpowiedzi na dwa pierwsze pytania poprawne, ostatnie nie ruszone. Od biedy mogę zaliczyć na żółto/pomarańczowo.

3Również na żółto/pomarańczowo zaliczam Roziemu, który odezwał się jako trzeci.

4Podobnie sytuacja wygląda ze zgłoszeniem Buttera, które nadeszło w chwilę później. Dwie poprawne odpowiedzi, trzeciej brak. Żółtopomarańczowo.

5W ostatniej chwili zgłosił się Waldek - i pozamiatał.

Żeby nie było nudno, swoje rozwiązanie na piechotę Waldek okrasił własną zagadką:

Komentarze