26  Problem Oturumu 8

Dinamik programlama oturumlarının ilki — SRTBOT ile dört problem: durum izleme, edit distance artı precomputation, LIS-benzeri blok kulesi ve yol sayma

NotOturum bilgisi
  • Solomon’un videosu: YouTube — Problem Session 8 (≈95 dk)
  • OCW sayfası: MIT 6.006 Problem Session 8
  • Seri: MIT 6.006 — Introduction to Algorithms (Spring 2020) — Ders 25 (PS8)
  • Hoca: Justin Solomon
  • Okuma süresi: ≈25 dk
  • DP problem oturumlarının İLKİ; tema: DP bir algoritma değil bir yaşam tarzı (Solomon 2:35).

26.1 Bu Problem Oturumu Ne Hakkında?

Sekizinci problem oturumu (Justin Solomon), dinamik programlama üzerine iki oturumun ilki; dört problemi SRTBOT çerçevesiyle çözer (Şekil 33.1). Tema sabit: DP “bir algoritma değil, bir yaşam tarzı” — recurrence yaz, alt problemleri memoize et.

“dynamic programming, it’s really more of a way of life than any particular algorithm.” — Solomon, 2:35

“when you have a recursive call and you repeat something… you might as well remember what you got the last time you saw that input.” — Solomon, 4:46

Dört problemde yeni kavramlar pekişir: alt problem durum izleme (kaç gün üst üste), edit distance (precomputation’ın çalışma süresine etkisi), LIS-benzeri sıralama-temelli DP, ve yol sayma (max yerine toplama). Her problem “İfade → Yaklaşım → Çözüm → Karmaşıklık” akışıyla işlenir.

flowchart TD
    R["Problem Oturumu 8<br/>(DP problem oturumlarının İLKİ)"] --> P1["Problem 1<br/>Tim the Beaver<br/>(suffix-DP + durum izleme)"]
    R --> P2["Problem 2<br/>Menix edit distance<br/>(suffix-çift + precomputation)"]
    R --> P3["Problem 3<br/>Saggy blok kulesi<br/>(sırala + LIS-benzeri)"]
    R --> P4["Problem 4<br/>Princess Plum<br/>(max-mushroom + yol sayma)"]
    CORE["Ortak tema:<br/>SRTBOT yaz, her alt problemi<br/>YEREL KABA KUVVETLE çöz<br/>(DP = bir yaşam tarzı)"]
    CORE -.-> P1
    CORE -.-> P2
    CORE -.-> P3
    CORE -.-> P4

    classDef root fill:#fef3c7,stroke:#b45309,stroke-width:3px,color:#1f2937
    classDef prob fill:#f3f4f6,stroke:#374151,stroke-width:2px,color:#1f2937
    classDef core fill:#fcd34d,stroke:#b45309,stroke-width:2px,color:#1f2937
    class R root
    class P1,P2,P3,P4 prob
    class CORE core
Şekil 26.1: Problem Oturumu 8’in kavram haritası: kök (PS8) dört probleme dallanır ve ortadaki ortak tema düğümü dördünü birden yönlendirir. Problem 1 Tim’in mutluluğunu suffix-DP ile maksimize eder ve ‘kaç gün üst üste’ kısıtını alt problem tanımına bir varsayım olarak gömerek durumu izler; Problem 2 Menix’in edit distance’ını suffix-çift DP ile çözer, satır karşılaştırmasının O(k) maliyetini precomputation’la çalışma süresine taşıyıp O(kn + n²)’ye iner; Problem 3 Saggy’nin blok kulesini üç gözlemle (uzun kenarı hizala, yönelim başına bir kez, kesin destek sıralanabilir kılar) LIS-benzeri bir DP’ye indirger; Problem 4 Princess Plum ızgarasını önce max-mushroom DP’siyle, sonra max yerine toplama yapan ikinci bir yol-sayma DP’siyle çözer. Ortak tema — SRTBOT yaz, her alt problemi yerel kaba kuvvetle çöz — Solomon’un dört probleme de aynı kapıdan girmesini sağlar.
İpucuYaklaşım — ortak strateji: SRTBOT yaz, her alt problemi yerel kaba kuvvetle çöz

Dört problemin tamamı aynı refleksle başlar: önce alt problemi (Subproblem) tanımla — tek dizide prefix/suffix, iki dizide sonek çifti, ızgarada hücre. Sonra “bu alt problemde hangi yerel kararı versem geri kalanı bir küçük alt probleme iner?” diye yerel kaba kuvvet uygula; bu recurrence’ı (Relation) verir. Kısıt varsa (Tim’in “kaç gün üst üste”, Saggy’nin “kesin destek”) onu ya alt problem tanımına bir varsayım olarak göm, ya da bir sıralama doğurup LIS’e in. “Kaç yol” sorulduğunda max yerine topla. Bu oturum, DP kasını bu dört SRTBOT’la çalıştırır — Solomon’un deyimiyle DP bir algoritma değil bir yaşam tarzıdır.

26.2 Problem 1: Tim the Beaver — Mutluluk DP

İfade. \(n\) gün, gün \(i\) sıcaklığı \(t(i)\). Dışarı çık → mutluluk \(+t(i)\); içeride kal → değişmez. En fazla 2 gün üst üste dışarı çıkılır. Mutluluğu maksimize et (ve planı kurtart).

İpucuYaklaşım — durum izleme: kısıtı alt problem tanımına bir varsayım olarak göm

Tek indeks (gün) → suffix DP. Kilit incelik: “kaç gün üst üste dışarı çıktım” durumunu açıkça izlemek yerine, alt problemi “i’de dışarı çıkma izni var” varsayımıyla tanımla. Böylece “en fazla 2 gün üst üste” kısıtı recurrence’a değil, alt problemin TANIMINA gömülür; üç yerel seçeneğin (içeride / tek gün çık / iki gün çık) her biri ileriyi taze-izinli bir güne devreder, durum bilgisi \(i\)’de saklı kalır.

Şekil 26.2 bu suffix-DP’yi motordan gerçek verilerle gösterir: örnek \(t = [5, -2, 8, 6, -4, 7, 3]\) için optimal plan gün 0’da tek (çık \(+5\)), gün 1 içeride (soğuk \(-2\) atla), gün 2-3 çift (\(8 + 6 = 14\)), gün 4 içeride (\(-4\) atla), gün 5-6 çift (\(7 + 3 = 10\)) → toplam \(5 + 14 + 10 = 29\). Bağımsız bitmask tanığı (3 ardışık günü yasaklayan kaba kuvvet) de aynı \(29\)’u verir.

Çözüm. S: $x(i) = $ \(i\)’de dışarı çıkma izniyle gün \(i \dots n\) maksimum mutluluk. R: üç seçenek:

  • içeride kal → \(x(i+1)\) (yarın tam serbest);
  • bugün çık, yarın içeride → \(t(i) + x(i+2)\);
  • bugün + yarın çık, ertesi içeride → \(t(i) + t(i+1) + x(i+3)\).

\(x(i)\) bu üç seçeneğin maksimumudur. T: \(i\) azalan. B: \(x(n+1) = x(n+2) = 0\). O: \(x(1)\). Planı kurtarmak için her gün hangi seçeneği aldığını sakla, sonra izle.

(İndeks köprüsü: prose, kaynaktaki gibi 1-indekslidir — günler \(1 \dots n\), cevap \(x(1)\). Aşağıdaki kod ve figür 0-indekslidir: cevap \(x(0)\), günler \(0 \dots n-1\); motor dizisi güvenlik payıyla üç sıfır taban tutar, 1-indeksli gösterimde \(x(n+1) = x(n+2) = 0\) yeterlidir.)

def tim_happiness(t):                            # O(n) — suffix DP
    n = len(t)
    x = [0] * (n + 3)                            # taban: x(n)=x(n+1)=x(n+2)=0
    for i in range(n - 1, -1, -1):               # i azalan (suffix sırası)
        x[i] = max(x[i + 1], t[i] + x[i + 2])    # içeride  /  bugün çık
        if i + 1 < n:
            x[i] = max(x[i], t[i] + t[i + 1] + x[i + 3])  # bugün + yarın çık
    return x                                     # x[0] = cevap

Karmaşıklık. \(n\) alt problem \(\times\ O(1) = O(n)\).

Şekil 26.2: Tim’in suffix-DP optimali — Problem 1 İMZA. Veri MOTORDAN (t=[5,−2,8,6,−4,7,3], x[0..6]=[29,24,24,16,10,10,3], plan [tek@0, çift@2, çift@5], bitmask brute = 29). ÜST: 7 gün şeridi — negatif sıcaklıklar slate dolgulu (soğuk, zorunlu mola), çıkılan günler amber çerçeveli; gün 0 TEK (dışarı +5), gün 2-3 ve 5-6 ÇİFT amber kemerleri (8+6=14, 7+3=10), gün 1 ve 4 içeride; ‘en fazla 2 gün üst üste’ kural rozeti; sağ toplam kutusu 5+14+10 = 29. ALT: x(i) suffix tablosu i=6→0 (sağdan sola çöz, soldan oku); her hücrede kazanan seçenek (içeride x(i+1) / tek t+x(i+2) / çift t+t’+x(i+3)) + amber ebeveyn okları; üç-seçenek lejantı; taban x(n)=x(n+1)=x(n+2)=0; ‘izin VAR varsayımı kısıtı alt-probleme göm’ notu. Alt not: Solomon 2:35 ‘way of life’ — kısıtı durum değişkenine taşı, recurrence yerel kalsın; O(n).

26.3 Problem 2: Menix Edit Distance — Precomputation

İfade. İki dosya \(A, B\) (\(n\) satır; her satır uzunluğu \(\le k\)). Satır ekle/sil pahalı (\(1\)), bitişik takas (swap) bedava (yalnız faydalıysa). Yalnız \(A\) düzenlenir. \(A\)’yı \(B\)’ye çeviren minimum takas-dışı işlem sayısı; hedef \(O(kn + n^2)\).

İpucuYaklaşım — klasik edit distance artı bir satır: precomputation’ı çalışma süresine taşı

Klasik edit distance DP’si suffix çiftiyle kurulur, üstüne küçük bir ek. Dosyayı satır satır, sırayla düzenle (atlamadan) → iki uçtan değil, suffix çift. Asıl incelik karmaşıklıkta: her \(A[i] = B[j]\) karşılaştırması iki satırın string eşitliğidir, yani \(O(k)\). Naif DP \(n^2\) alt problemin her birinde \(O(k)\) iş yapar. Hile: satırları önce \(O(k)\) ile bir kimliğe (hash) ön-işle (toplam \(kn\)), sonra DP’de \(O(1)\) karşılaştır (\(n^2\)) → \(O(kn + n^2)\). Çalışma süresindeki \(k\), satır karşılaştırmasının ipucudur.

“the way that I would edit a file… would be linearly.” — Solomon, 39:33

Çözüm. S: \(x(i, j) = A[i:] \to B[j:]\) minimum iş. R (min, dört durum): \(A[i] = B[j] \to x(i+1, j+1)\); \(A[i]\) sil \(\to 1 + x(i+1, j)\); satır ekle (\(B[j]\) eşle) \(\to 1 + x(i, j+1)\); takas (\(A[i+1] = B[j] \wedge A[i] = B[j+1]\)) \(\to x(i+2, j+2)\). T: \(i+j\) artan (2B tabloda sağ-aşağı). B: \(x(n+1, j) = n+1-j\); \(x(i, n+1) = n+1-i\). O: \(x(1, 1)\).

Örnek \(A = [\alpha, \beta, \gamma, \delta]\), \(B = [\beta, \alpha, \gamma, \varepsilon]\) için cevap \(2\): takas (\(\alpha, \beta\)) bedava + \(\gamma\) eşle + \(\delta\) sil + \(\varepsilon\) ekle. Bu DP’nin doğruluğunu ikinci bir çerçeve teyit eder: edit distance, durum \((i, j)\)’leri düğüm, eşle/takas kenarları \(0\)-ağırlık, sil/ekle kenarları \(1\)-ağırlık olan bir grid-DAG üzerinde en kısa yoldur (bu, Ders 24’ün LCS-grid’inin ve Ders 23’te kurulan “her DP bir DAG’da en kısa yola indirgenebilir” köprüsünün doğrudan akrabasıdır). Motorda bu grid-DAG’ı \((0,0) \to (n, n)\) koşan bağımsız tanık bir Dijkstra’dır; aynı örnekte o da \(2\) verir, ve önce kimliklenmiş satırlarla koşulan precomputation sürümü de aynı cevabı üretir.

def edit_distance_swaps(A, B):                   # O(n²) alt problem; precompute → O(kn + n²)
    na, nb = len(A), len(B)
    x = {}
    for j in range(nb + 1): x[(na, j)] = nb - j  # taban: kalanı ekle
    for i in range(na + 1): x[(i, nb)] = na - i  # taban: kalanı sil
    for i in range(na - 1, -1, -1):
        for j in range(nb - 1, -1, -1):
            best = min(1 + x[(i + 1, j)], 1 + x[(i, j + 1)])   # sil / ekle
            if A[i] == B[j]:                                   # eşle (bedava)
                best = min(best, x[(i + 1, j + 1)])
            if i + 1 < na and j + 1 < nb \
                    and A[i + 1] == B[j] and A[i] == B[j + 1]:  # takas (bedava)
                best = min(best, x[(i + 2, j + 2)])
            x[(i, j)] = best
    return x[(0, 0)]

Karmaşıklık. \(n^2\) alt problem \(\times\ O(1)\)ama her \(A[i] = B[j]\) satır karşılaştırması \(O(k)\). Satırları önce \(O(k)\) ile bir kimliğe ön-işle (toplam \(kn\)), sonra DP’de \(O(1)\) karşılaştır (\(n^2\)) → \(O(kn + n^2)\). (Bu, “alt problem \(\times\) iş” formülüne ön-işlemenin eklendiği nadir bir durumdur.)

26.4 Problem 3: Saggy’nin Blok Kulesi — LIS-benzeri

İfade. \(n\) blok, her biri \(w \times h \times l\) (istenildiği gibi döndürülebilir; her tipten \(\ge 3\) adet). Maksimum kule yüksekliği; her blok altındakinde kesin (strict) destekli olmalı.

İpucuYaklaşım — kesin destek bir sıralama doğurur: sırala, sonra LIS-benzeri DP koş

\(2^n\) olası alt küme gibi görünür; ama üç gözlemle LIS’e indirger. (1) Uzun-kenarı uzun-kenara hizala → her taban sıralı bir (küçük, büyük) çifti. (2) Kesin destek → her yönelim (hangi eksen yukarı) en fazla bir kez kullanılır → her bloktan en fazla 3 yönelim. (3) Destek koşulu, taban kenarlarını her katta azaltır → liste sıralanabilir. Sıraladıktan sonra “alt blok daha büyük taban” zinciri tam olarak en uzun artan alt-dizilim kalıbıdır; yerel kaba kuvvetle her yönelimi tepe yapıp altına gelebilecek en yüksek alt-kuleyi ara.

“we can sort by the edge lengths because we know that we have this support condition.” — Solomon, 1:03:09

Şekil 26.3 bu indirgemeyi motordan gerçek verilerle gösterir: iki blok tipi \(\{1 \times 2 \times 3,\ 2 \times 3 \times 4\}\) altı yönelime açılır, taban azalan lexicographic sıralanır; LIS-benzeri DP en yüksek kuleyi \(9\) bulur — taban \((3, 4)\) dikey \(2\), üstüne \((2, 3)\) dikey \(4\), üstüne \((1, 2)\) dikey \(3\) (\(2 + 4 + 3 = 9\)). Bağımsız zincir-DFS tanığı da \(9\) verir.

Çözüm. Her bloğu sıralı (\(w \le h \le l\)) 3 yönelime aç, listeyi lexicographic sırala, tekrarları çıkar. S: $x(i) = $ blok \(i\)’yi tepe yaparak ilk \(i\) blokla kurulabilen maksimum yükseklik. R: \(x(i) = v_i + \max(\{x(j) : j < i,\ a_i < a_j,\ b_i < b_j\} \cup \{0\})\), burada \((a_i, b_i)\) yönelim \(i\)’nin tabanı ve \(v_i\) dikey kenarıdır; koşul \(a_i < a_j \wedge b_i < b_j\), blok \(i\)’nin blok \(j\)’nin üstüne kesin destekle sığması demektir. O: \(\max_i x(i)\). B: \(x(1) = v_1\).

def tower_height(blocks):                        # O(n log n) + n×O(n) = O(n²)
    ori = block_orientations(blocks)             # sıralı 3 yönelim/blok, lex sıralı
    n = len(ori)
    x = [0] * n
    for i in range(n):
        a, b, v = ori[i]
        best = 0
        for j in range(i):                       # LIS-benzeri tarama
            aj, bj, _ = ori[j]
            if a < aj and b < bj:                # kesin destek: İKİ kenar da küçük
                best = max(best, x[j])
        x[i] = v + best
    return max(x), ori, x

Karmaşıklık. Ön sıralama \(O(n \log n)\) + \(n\) alt problem \(\times\ O(n)\) iş = \(O(n^2)\).

Şekil 26.3: Saggy’nin blok kulesi: sırala + LIS-benzeri DP — Problem 3. Veri MOTORDAN ({1×2×3, 2×3×4} → 6 yönelim, kule = 9, zincir [0,2,5], zincir-DFS brute = 9). SOL: kazanan 3-katlı kule kesiti (yandan) — taban (3,4) dikey 2 (en geniş), üstüne (2,3) dikey 4, üstüne (1,2) dikey 3; genişlikler taban-a ile orantılı KESİN küçülür ((3,4)⊃(2,3)⊃(1,2)); sağda yükseklik ölçek çubuğu toplam = 9; sol kenarda ‘kesin destek: iki taban kenarı da küçük’ notu. SAĞ: 6 yönelimin azalan-lex tablosu (taban-a, taban-b, dikey v, x(i)); kazanan satırlar (i=0,2,5) amber; sola yaylı LIS-zinciri okları 0→2→5 (2+4+3=9); altta üç gözlem + Solomon 1:03:09. Alt başlık: yükseklik = 9.

26.5 Problem 4: Princess Plum Izgarası — Yol Sayma

İfade. \(n \times n\) ızgara; her hücre mushroom / ağaç / boş. Sol-üstten sağ-alta hızlı yol (\(2n - 1\) hücre — yalnız aşağı/sağ; ağaca girilmez). (a) Toplanabilecek maksimum mushroom \(k\); (b) \(k\) mushroom toplayan hızlı yol sayısı.

İpucuYaklaşım — iki ardışık DP: önce hedefi hesapla, sonra max yerine toplayarak say

“Hızlı” = tam \(2n - 1\) hücre → yalnız aşağı ve sağ (yukarı/sol path’i uzatır). Izgara zaten bir DAG (sağ-aşağı). İki ardışık DP gerekir. (a) max mushroom için klasik ızgara DP: her hücreye gelen iki komşudan en iyisini al. (b) “kaç yol” sorusu kritik bir değişiklik ister: recurrence’da max yerine toplama — bir komşu optimal mushroom sayısına ulaşıyorsa onun yol sayısını ekle. Pozitif sayılar yalnız taban durumdan doğar; \(1\)’lerin kaynağı \(x(0, 0) = 1\)’dir.

“she can only move down and to the right because if she moved up or to the left, her path would no longer be called quick.” — Solomon, 1:21:32

Şekil 26.4 bu iki DP’yi motordan gerçek verilerle gösterir: \(4 \times 4\) örnekte max mushroom \(k = 2\) ve onu toplayan tam 3 hızlı yol vardır. Bağımsız tüm-monoton-yollar tanığı da \((2, 3)\) verir; ek bir kontrol olarak tamamen kapalı bir \(2 \times 2\) ızgara \(k = -\infty\) ve yol sayısı \(0\) döndürür (taban dışında pozitif sayı doğmaz).

Çözüm. (a) max mushroom: \(k(i, j) = \chi(i, j) + \max(k(i-1, j),\ k(i, j-1))\); ağaç \(\to -\infty\). ($= $ hücrede mushroom varsa \(1\), yoksa \(0\).) Base: \(k(1, 1) = 0\). (b) yol sayısı: \(x(i, j) = (i, j)\)’de biten, \(k(i, j)\) mushroom toplayan hızlı yol sayısı. Soldan ve/veya yukarıdan gelen bir komşu için, o komşunun \(k\) değeri artı \(\chi(i, j)\) tam olarak \(k(i, j)\)’ye eşitse, o komşunun yol sayısını \(x(i, j)\)’ye ekle (max değil, toplama — yol sayıyoruz). Base: \(x(1, 1) = 1\). O: \(x(n, n)\). Pozitif sayılar yalnız taban durumdan doğar.

“all of the reason why the positive numbers appear in this problem is from the base case.” — Solomon, 1:33:02

def plum_count_paths(grid):                      # (b) yol-sayma DP
    n, m = len(grid), len(grid[0])
    k = plum_max_mushroom(grid)                  # (a) önce max mushroom (ayrı DP)
    x = {}
    for i in range(n):
        for j in range(m):
            if grid[i][j] == "T" or k[(i, j)] == -INF:
                x[(i, j)] = 0; continue
            if i == 0 and j == 0:
                x[(i, j)] = 1; continue          # taban: pozitiflerin TEK kaynağı
            chi = 1 if grid[i][j] == "m" else 0
            cnt = 0
            if i > 0 and k[(i - 1, j)] + chi == k[(i, j)]: cnt += x[(i - 1, j)]  # TOPLA
            if j > 0 and k[(i, j - 1)] + chi == k[(i, j)]: cnt += x[(i, j - 1)]  # max DEĞİL
            x[(i, j)] = cnt
    goal = (n - 1, m - 1)
    return k[goal], x[goal], x

Karmaşıklık. İki DP, her biri \(n^2\) hücre \(\times\ O(1)\)\(O(n^2)\).

Şekil 26.4: Princess Plum: max-mushroom DP → yol-sayma DP — Problem 4 İMZA. Veri MOTORDAN (4×4 grid, max k=2, TAM 3 yol, tüm-monoton-yollar brute = (2,3)). SOL: 4×4 ızgara — mushroom (amber m), ağaç (koyu T, geçilmez), boş (beyaz); S başlangıç, H hedef; ÜÇ optimal yol farklı çizgi stiliyle (her biri 2 mushroom: üst m-çifti 1 rota + sol-alt m-çifti 2 rota). SAĞ-ÜST: k(i,j) max-mushroom DP tablosu (k = χ + max(yukarı, sol); ağaç → −∞). SAĞ-ALT: x(i,j) yol-sayma DP tablosu (k eşleşirse TOPLA, max DEĞİL); pozitif sayılar amber, taban x(0,0)=1 amber çerçeveli — pozitiflerin TEK kaynağı (Solomon 1:33:02). Alt: iki ardışık DP, her biri O(n²).

26.6 Ne Öğrendik?

ÖnemliAltı Taşınabilir Araç

Bu oturum, DP problem oturumlarının ilkiydi ve SRTBOT çerçevesini dört somut problemde uyguladı:

  1. Durum izleme (Tim): “kaç gün üst üste” gibi bir kısıtı, alt problem tanımına bir varsayım (“i’de izin var”) gömerek izle.
  2. Edit distance: suffix-çift DP; ekle/sil/eşle/takas durumları min ile; \(O(n^2)\).
  3. Precomputation runtime’a girer: satır karşılaştırması \(O(k)\) → önce hash (\(kn\)), sonra DP (\(n^2\)) = \(O(kn + n^2)\); “alt problem \(\times\) iş” formülüne ön-işleme eklenir.
  4. Sıralama-temelli DP (blok): kısıt bir sıralama doğuruyorsa, sırala → problem LIS’e iner.
  5. Yol sayma (Plum): “kaç yol” sorulduğunda recurrence’da max yerine toplama; pozitiflik taban durumdan gelir.
  6. İki ardışık DP: önce hedefi (max mushroom \(k\)) hesapla, sonra onu kullanan ikinci DP (yol sayısı).

26.7 Sonraki

UyarıDers 26 (L17): Dinamik Programlama 3 — Demaine

Sırada Ders 26 (L17): Dinamik Programlama 3 var — Erik Demaine ile, DP’yi ağaç ve daha karmaşık alt problemlere taşıyoruz. Bu oturumda gördüğümüz subproblem expansion ve durum izleme, ağaç alt problemlerinde ve pseudopolinom örneklerde derinleşir.