From 008bb2d36f5310b09fc3f699f1b2d1a2c862898e Mon Sep 17 00:00:00 2001
From: Martin Mares <mj@ucw.cz>
Date: Wed, 20 May 2020 11:37:45 +0200
Subject: [PATCH] =?UTF-8?q?Dynamick=C3=A9=20programov=C3=A1n=C3=AD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 12-dp/obdelnik-12.py      | 34 ++++++++++++++++
 12-dp/obdelnik-1k.py      |  7 ++++
 12-dp/rozklad-na-slova.py | 82 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 123 insertions(+)
 create mode 100644 12-dp/obdelnik-12.py
 create mode 100644 12-dp/obdelnik-1k.py
 create mode 100644 12-dp/rozklad-na-slova.py

diff --git a/12-dp/obdelnik-12.py b/12-dp/obdelnik-12.py
new file mode 100644
index 0000000..dab4351
--- /dev/null
+++ b/12-dp/obdelnik-12.py
@@ -0,0 +1,34 @@
+#!/usr/bin/python3
+# Počet dláždění obdélníka 1×n kostkami 1×1 a 1×2
+
+# Rekurzivní funkce s exponenciální složitostí
+def d(n):
+    if n <= 1:
+        return 1
+    return d(n-1) + d(n-2)
+
+# Všimli jsme si, že d() počítá spoustu podproblémů opakovaně,
+# tak jsme dodělali kešování známých mezivýsledků. Tím se časová
+# složitost zlepšila na O(n), ale chvíli trvá to dokázat.
+pamet = { 0: 1, 1: 1 }
+def d2(n):
+    if n not in pamet:
+        pamet[n] = d2(n-1) + d2(n-2)
+    return pamet[n]
+
+# Tady počítáme tytéž podproblémy od nejmenšího k největšímu.
+# Rekurzi jsme nahradili obyčejným cyklem, složitost je evidentně O(n).
+def d3(n):
+    D = [1, 1] + [0]*n
+    for i in range(2, n+1):
+        D[i] = D[i-1] + D[i-2]
+    return D[n]
+
+# Konečně jsme si všímli, že není potřeba pamatovat si všechny
+# mezivýsledky, ale stačí předchozí dva. Tím jsme snížili prostorovou
+# složitost na konstantní.
+def d4(n):
+    a = b = 1
+    for i in range(2, n+1):
+        a, b = a+b, a
+    return a
diff --git a/12-dp/obdelnik-1k.py b/12-dp/obdelnik-1k.py
new file mode 100644
index 0000000..20322a1
--- /dev/null
+++ b/12-dp/obdelnik-1k.py
@@ -0,0 +1,7 @@
+#!/usr/bin/python3
+# Počet dláždění obdélníka 1×n kostkami 1×k (kde k je libovolné)
+
+def d(n):
+    if n == 0:
+        return 1
+    return sum(d(i) for i in range(n))
diff --git a/12-dp/rozklad-na-slova.py b/12-dp/rozklad-na-slova.py
new file mode 100644
index 0000000..b4fd1ab
--- /dev/null
+++ b/12-dp/rozklad-na-slova.py
@@ -0,0 +1,82 @@
+#!/usr/bin/python3
+# Rozklad řetězce na posloupnost slov ze slovníku.
+
+# Načítání slovníku ze souboru
+
+slovnik = set()
+
+def nacti_slovnik():
+    with open('slovnik') as f:
+        for slovo in f:
+            slovnik.add(slovo.strip())
+
+# První pokus: rekurzivní rozkládání. Zkoušíme všechny možnosti,
+# jaké slovo může být první, a pro každou z nich rekurzivně zjistíme,
+# zda se zbytek řetězce dá rozložit. V nejhorším případě to trvá
+# exponenciálně dlouho (například pro slovník {"a","aa"} a řetězec
+# ze samých a-ček je tato úloha ekvivalentní s rozkladem obdélníka,
+# který jsme už zkoumali).
+def rozloz(retezec):
+    if retezec == "":
+        return True
+    for i in range(len(retezec)+1):
+        if retezec[:i] in slovnik and rozloz(retezec[i:]):
+                return True
+    return False
+
+# Všimli jsme si, že všechny podproblémy jsou suffixy původního řetězce,
+# takže jako parametr místo řetězce předáváme pozici v původním řetězci
+# (začátek suffixu). Také bychom si mohli všimnout (ale ještě jsme toho
+# nevyužili), že možných podproblémů je málo a začít kešovat.
+def rozloz2(retezec):
+
+    def roz(p):
+        """Rozlož retezec[p:]"""
+        if p == len(retezec):
+            return True
+        return any(retezec[p:q] in slovnik and roz(q) for q in range(p+1, len(retezec)+1))
+
+    return roz(0)
+
+# Podproblémy řešíme od nejkratšího suffixu k nejdelšímu, rekurzi jsme
+# nahradili cyklem. Časová složitost činí O(n³).
+def rozloz3(retezec):
+
+    n = len(retezec)
+    umim = [None]*(n+1)
+    umim[n] = True
+
+    for p in range(n-1, -1, -1):
+        umim[p] = any(retezec[p:q] in slovnik and umim[q] for q in range(p+1, n+1))
+
+    return umim[0]
+
+# A teď nás zajímá nejen, jestli rozklad existuje, ale také jak nějaký konkrétní
+# rozklad vypadá. Pro každý suffix si zapamatujeme, které slovo (stačí jeho délka)
+# fungovalo jako první v suffixu. Podle toho pak rozklad sestrojíme.
+def rozloz4(retezec):
+
+    n = len(retezec)
+    umim = [None]*(n+1)
+    umim[n] = 0
+
+    # Hlavní výpočet
+    for p in range(n-1, -1, -1):
+        for q in range(p+1, n+1):
+            if retezec[p:q] in slovnik and umim[q] is not None:
+                umim[p] = q-p
+
+    # Rekonstrukce rozkladu
+    if umim[0] is None:
+        return None
+    else:
+        rozklad = []
+        p = 0
+        while p < n:
+            rozklad.append(retezec[p:p+umim[p]])
+            p += umim[p]
+        return rozklad
+
+    return umim[0]
+
+nacti_slovnik()
-- 
GitLab