Problem 67

Problem 67

By starting at the top of the triangle below and moving to adjacent numbers on the row below, the maximum total from top to bottom is 23.

[color=red]3[/color]

[color=red]7[/color] 5
2 [color=red]4[/color] 6
8 5 [color=red]9[/color] 3

That is, 3 + 7 + 4 + 9 = 23.

Find the maximum total from top to bottom in triangle.txt (right click and ‘Save Link/Target As…’), a 15K text file containing a triangle with one-hundred rows.

NOTE: This is a much more difficult version of Problem 18. It is not possible to try every route to solve this problem, as there are 299 altogether! If you could check one trillion (1012) routes every second it would take over twenty billion years to check them all. There is an efficient algorithm to solve it. ;o)

O, ciekawe. Coś na algorytm Dijkstry? Bo prócz niego do łba mi przychodzą wyłącznie metody ewolucyjne, czyli słabe :wink:

po zauważeniu, że lepiej liczyć opcje “od dołu piramidy”, zadanie przestaje być poważnym wyzwaniem. Rozwiązanie pokazane przez luckboy’a w problemie 18-stym tutaj również działa.

To czy liczysz od góry czy od dołu nie ma znaczenia, ważne żeby zapamiętywać już obliczone sumy do danego poziomu

yyy… bardzo chętnie zobaczę algorytm liczący zadanie “od góry” - z zapamiętywaniem. Liczenie od dołu ma tą poważną zaletę, że niczego nie musisz zapamiętywać. Na bieżąco odrzucasz mniejszą sumę i kwalifikujesz większą do liczenia przy kolejnym wierszu.

gRuby, nie. W dużym skrócie nie. Największa liczba w dolnym rzędzie wcale nie musi być najlepszym punktem rozpoczęcia trasy (np. wyobraź sobie że sąsiadują z nią same jedynki w rzędzie wyżej). To jest właśnie clou całej złożoności problemu.

Ciekawe, prosimy o wyjaśnienie “krok po kroku” dla dummies. Wiem, że algorytm od dołu działa dobrze ale za cholerę nie mogę go zrozumieć (obwiniam poniedziałkowy poranek :slight_smile: )

jeśli czegoś nie rozumiem, proszę o wyjaśnienie;
mój sposób rozumowania:

  • zaczynamy od najniższego wiersza i wiersza powyżej,
  • dla każdego utworzonego “trójkąta” (liczba z wiersza wyższego + dwie liczby z wiersza poniżej) robimy dwie sumy i do dalszych obliczeń używamy tej większej.

jeśli ten tok rozumowania jest słaby to wykazałem się niesamowitym szczęściem licząc oba zadania (18, 67) prawidłowo w prostym skrypcie w minimalnym (jak można się domyślić) czasie …

Miałeś szczęśliwe dane wejściowe, to wszystko.

OK, zacytuję rozwiązanie luckboy’a:

[code]#!/usr/bin/env ruby
s = "
75
95 64
17 47 82
18 35 87 10
20 04 82 47 65
19 01 23 75 03 34
88 02 77 73 07 63 67
99 65 04 28 06 16 70 92
41 41 26 56 83 40 80 70 33
41 48 72 33 47 32 37 16 94 29
53 71 44 65 25 43 91 52 97 51 14
70 11 33 28 77 73 17 78 39 68 17 57
91 71 52 38 17 14 91 43 58 50 27 29 48
63 66 04 68 89 53 67 30 73 16 69 87 40 31
04 62 98 27 23 09 70 98 73 93 38 53 60 04 23"

#s = "

3

7 5

2 4 6

#8 5 9 3"

t = s.strip.split("\n").collect { |l| l.split(" ").collect { |s| s.to_i } }

(t.length-2).downto(0) { |i| 0.upto(t[i].length-1) { |j| t[i][j] += t[i+1][j…j+1].max } }
p t[0][0][/code]
Rozwiązanie wydaje mi się poprawne dla każdego trójkąta Tomash, chociaż wciąż go nie rozumiem, umysł zniszczony pisaniem CMS’ów w Railsach poddaje się przy algorytmice - to straszne uczucie :slight_smile:

Rozwiązanie w Haskell’u (podsyłam w nadziei, że przejrzystość kodu Haskell’a - brak zmiennych i j - rozjaśni mi umysł i lepiej zobrazuje algorytm):

problem_67 = readFile "triangle.txt" >>= print . solve . parse parse = map (map read . words) . lines solve = head . foldr1 step step [] [z] = [z] step (x:xs) (y:z:zs) = x + max y z : step xs (z:zs)

oto moja (bardziej toporna) wersja rozwiązania:

rows = [] File.open("triangle.txt").each do |t| rows << t.split(' ') end down_row = rows[rows.size-1] (rows.size-1).downto(1) do |p| new_row = [] upper_row = rows[p-1] 0.upto(upper_row.size-1) do |s| left_sum = down_row[s].to_i + upper_row[s].to_i right_sum = down_row[s+1].to_i + upper_row[s].to_i if left_sum > right_sum new_row << left_sum else new_row << right_sum end end down_row.replace new_row puts "oto nowe watrości #{down_row}" end
dla jasności po każdej pętli pokazuję każdorazowo tablicę z nowymi wartościami maksymalnymi. Ostatnia wartość jest rozwiązaniem.

[quote=Tomash]gRuby, nie. W dużym skrócie nie…
Miałeś szczęśliwe dane wejściowe, to wszystko.[/quote]
Czy mogę Ciebie prosić Tomash o wskazanie błędu w rozumowaniu, ewentualnie o podanie mniej szczęśliwych danych ?

Co prawda nie jestem Tomashem, ale mogę podać takie dane :wink:

[color=red]3[/color]

[color=red]1[/color] 9
[color=red]9[/color] 1 1
[color=red]9[/color] 1 1 1

3 + 1 + 9 + 9 = 22

U Ciebie wyszłoby 3 9 1 1.

Nie, gRubego metoda polega na spacerze od dołu. Jeśli dobrze zrozumiałem :slight_smile:

nieprawda

Ok, sorry, mogłem to sprawdzić :slight_smile:

Nawet nie spojrzałem na algorytm tylko zasugerowałem się tym co napisał Tomash - mój błąd :slight_smile:

Algorytm jest bardzo prosty :). Podstawowe założenie jest takie, że do każdego miejsca w trójkącie można tak naprawdę dojść albo z lewego górnego sąsiada albo z prawego (no może poza drugim wierszem). No i teraz największa suma dla danego miejsca to suma dojścia do lewego albo prawego sąsiada plus wartość w danym miejscu.

Algorytm dla każdego wiersza leci po wszystkich pozycjach i dla każdej wybiera większą sumę z jego sąsiadów, dodaje swoją wartość i zapisuje największą sumę dla danego wiersza. Chyba trochę zakombinowałem :slight_smile:

Dla tych danych co podał drogus te sumy wyglądały by tak:

3                       3

1 9 4 12
9 1 1 13 13 13
9 1 1 1 22 14 14 14

A tu algorytm liczący od góry, jest trochę mało zwięzły ale działa:

[code=ruby]def max(a,b)
a >= b ? a : b
end

File.open(‘triangle.txt’) do |f|
lines = f.readlines
last = []
lines.each_with_index do |line, i|
index = i +1
current = line.split(’ ').map(&:to_i)
size = current.size
new_line = []
if index == 1
last[0] = current[0]
else
current.each_with_index do |value, j|
if j == 0
new_line[0] = last[0] + current[0]
elsif j == (size - 1)
new_line[j] = last[j-1] + current[j]
else
new_line[j] = current[j] + max(last[j-1], last[j])
end
end
last = new_line
end
end
puts last.sort.last
end[/code]

Damn, w takim razie sorry, gRuby – miałeś rację, a ja wracam się douczać algorytmiki :slight_smile: