動的計画法(ウサギの足場)

問題

$ N $ 個の足場があり、左から $ i $ 番目の足場(足場 $ i $ とする)の高さは $ h_i $ です。

ウサギは以下の2種類の行動を繰り返すことで、足場1から足場 $ N $ に移動します。

 🔹足場 $ i - 2 $ から足場 $ i $ に、コスト $ | h_{i-2} - h_i | $ かけて移動する。
 🔹足場 $ i - 1 $ から足場 $ i $ に、コスト $ | h_{i-1} - h_i | $ かけて移動する。

移動にかかる合計コストの最小値を出力して下さい。

解き方・ソースコード

この問題は、足場1から足場 $ i $ までの最小コストを $ dp[i-1] $ とし、$ dp[0] → dp[1] → ・・・ → dp[N-1] $ の順に1つずつ計算すると、答えを出すことができます。

配列 $ dp $ の計算方法としては、まず足場1から足場1までは移動する必要がないので、$ dp[0] = 0 $ となります。

次に足場1から足場2まで行くには直接移動するしかないので、$ dp[1] = h_0 - h_1 $ となります。

$ dp[2] $ 以降は最後の行動で場合分けをして考えます。足場 $ i $ まで移動するには、次の2つが考えられます。

 (方法1)足場 $ i - 1 $ まで移動した後、1つ隣りの足場 $ i $ に行く。
 (方法2)足場 $ i - 2 $ まで移動した後、2つ隣りの足場 $ i $ に行く。

ここで、方法1を選んだ時の合計コストは $ dp[i - 1] + abs(H[i - 1] - H[i]) $ であり、方法2を選んだ時の合計コストは $ dp[i - 2] + abs(H[i - 2] - H[i]) $ です。

コストの少ないほうを求める問題なので、$ dp[i] $ の値は次式のように表すことができます。

$$ dp[i] = min(dp[i-1] + abs(H[i - 1] - H[i]), dp[i-2] + abs(H[i - 2] - H[i])) $$

上記の内容を踏まえてコーディングすると下記のようなソースコードになります。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#--------- 入力例 ----------
N = 6
H = [30, 10, 60, 10, 60, 50]
#---------------------------
# 配列の準備
dp = [ None ] * N

# 動的計画法
dp[0] = 0
print('足場1までの最小コスト', dp)

dp[1] = abs(H[0] - H[1])
print('足場2までの最小コスト', dp)

for i in range(2, N): # 足場3以降の最小コスト
dp[i] = min(dp[i - 1] + abs(H[i - 1] - H[i]), dp[i - 2] + abs(H[i - 2] - H[i]))
print('足場{}までの最小コスト'.format(i + 1), dp)

# 出力
print('解:', dp[N - 1])

[実行結果(入力例1)]

足場1までの最小コスト [0, None, None, None, None, None]

足場2までの最小コスト [0, 20, None, None, None, None]

足場3までの最小コスト [0, 20, 30, None, None, None]

足場4までの最小コスト [0, 20, 30, 20, None, None]

足場5までの最小コスト [0, 20, 30, 20, 30, None]

足場6までの最小コスト [0, 20, 30, 20, 30, 40]

解: 40

足場1→足場3→足場5→足場6と移動すると最小コスト40で移動することができます。

動的計画法(部屋移動)

問題

$ N $ 個の部屋があり、$ 1 $ から $ N $ までの番号が付けられています。

この部屋はすべて一方通行であり、通路を介して1つ先または2つ先の部屋に移動できます。

各通路における移動時間は以下の通りです。

🔹部屋 $ i - 1 $ から部屋 $ i $ に向かう通路を通るのに $ A_i $ 分 $ (2 \leqq i \leqq N) $ かかる
🔹部屋 $ i - 2 $ から部屋 $ i $ に向かう通路を通るのに $ B_i $ 分 $ (3 \leqq i \leqq N) $ かかる

部屋 $ 1 $ から部屋 $ N $ に移動するのに、最短何分かかるでしょうか。

[制約]
🔹$ 3 \leqq n \leqq 100000 $
🔹$ 1 \leqq A_i \leqq 100  (2 \leqq i \leqq N) $
🔹$ 1 \leqq B_i \leqq 100  (3 \leqq i \leqq N) $

解き方・ソースコード

この問題は、部屋1から部屋 $ i $ までの最短時間を $ dp[i] $ とし、$ dp[1] → dp[2] → ・・・ → dp[N] $ の順に1つずつ計算すると、答えを出すことができます。

配列 $ dp $ の計算方法としては、まず部屋1から部屋1までは移動する必要がないので、$ dp[1] = 0 $ となります。

次に部屋1から部屋2まで行くには直接移動するしかないので、$ dp[2] = A_2 $ となります。

$ dp[3] $ 以降は最後の行動で場合分けをして考えます。部屋 $ i $ まで移動するには、次の2つが考えられます。

 (方法1)部屋 $ i - 1 $ まで移動した後、1本の通路を通って部屋 $ i $ に行く。
 (方法2)部屋 $ i - 2 $ まで移動した後、1本の通路を通って部屋 $ i $ に行く。

ここで、方法1を選んだ時の合計時間は $ dp[i - 1] $ 分であり、方法2を選んだ時の合計時間は $ dp[i - 2] $ 分です。

時間の短いほうを求める問題なので、$ dp[i] $ の値は次式のように表すことができます。

$$ dp[i] = min(dp[i-1] + A_i, dp[i-2] + B_i) $$

上記の内容を踏まえてコーディングすると下記のようなソースコードになります。

A, B が 0 番目から始まっていることに注意して下さい。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#--------- 入力例 ----------
N = 5
A = [2, 4, 1, 3]
B = [5, 3, 7]
#---------------------------
# 動的計画法
dp = [ None ] * (N + 1)

dp[1] = 0 # 部屋1までの最短時間
print('部屋1番目までの最短時間', dp)

dp[2] = A[0] # 部屋2までの最短時間
print('部屋2番目までの最短時間', dp)

for i in range(3, N + 1):
dp[i] = min(dp[i - 1] + A[i - 2], dp[i - 2] + B[i - 3])
print('部屋{}番目までの最短時間'.format(i), dp)
# 出力
print('解:', dp[N])

[実行結果]

部屋1番目までの最短時間 [None, 0, None, None, None, None]

部屋2番目までの最短時間 [None, 0, 2, None, None, None]

部屋3番目までの最短時間 [None, 0, 2, 5, None, None]

部屋4番目までの最短時間 [None, 0, 2, 5, 5, None]

部屋5番目までの最短時間 [None, 0, 2, 5, 5, 8]

解: 8

部屋1から部屋5までの最短時間が8分であることを導き出すことができました😊

余りの計算(黒板)

問題

黒板に0という整数が書かれています。

その整数に対して $ N $ 回の操作を行います。

$ i $回目の操作は文字$ T_i $と整数$ A_i $で表され、その操作内容は次の3種類です。

🔹$ T_i = + $ のとき:$ A_i $ を足す。
🔹$ T_i = - $ のとき:$ A_i $ を引く。
🔹$ T_i = * $ のとき:$ A_i $ を掛ける。

各操作が終わった後について、黒板に書かれた整数を10000で割った余りを出力するプログラムを作成して下さい。

[制約]
🔹$ 1 \leqq n \leqq 100000 $
🔹$ 1 \leqq A_i \leqq 100 $
🔹黒板に書かれる整数は常に0以上

解き方・ソースコード

この問題はオーバーフローを防ぐために操作が行われるたびに余りをとるということがポイントとなります。

ただし引き算を行う場合、計算結果がマイナスになってしまう可能性がるため、計算の途中で0を下回った場合10000を足す必要があります。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#--------- 入力例1 ----------
N = 4
A = ['+ 59',
'+ 41',
'* 100',
'- 1']
#----------------------------
# 現在の黒板の数
Answer = 0
for a in A:
ss = a.split()
if ss[0] == '+':
Answer += int(ss[1])
elif ss[0] == '-':
Answer -= int(ss[1])
elif ss[0] == '*':
Answer *= int(ss[1])

# 引き算で答えが 0 未満になった場合
if Answer < 0:
Answer += 10000

# 余りをとる
Answer %= 10000
print(Answer)

[実行結果(入力例1)]

59
100
0
9999

解が非常に大きくなる問題では、余りを出力するということがよくあるので上記の手法を覚えておくことはとても有益です。

全探索

問題

$ N $ 個の商品があり、商品 $ i (1 \leqq i \leqq N)$の価格は $ A_i $ 円です。

異なる3つの商品を選び、合計価格をちょうど1000円にすることができるでしょうか。

答えをYesNoで出力して下さい。

[制約]
🔹$ 3 \leqq n \leqq 100 $

解き方・ソースコード

この問題は3重ループを使うことで解くことができます。

1つめのループは全商品をチェックするために、$ N $ 回実行します。

2つめのループは1つめの商品と同じ商品を選ばないように、$ N - 1 $ 回実行します。

3つめのループは1つめ、2つめの商品と同じ商品を選ばないように、$ N - 2 $ 回実行します。

3重ループの中では、選んだ3つの商品の合計が1000円かどうかを確認します。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#--------- 入力例1 ----------
N = 3
A = [200, 300, 400]
#--------- 入力例2 ----------
# N = 5
# A = [100, 200, 300, 400, 500]
#----------------------------
def check(n, a):
for i in range(n):
for j in range(i + 1, n):
for k in range(j + 1, n):
if a[i] + a[j] + a[k] == 1000:
return "Yes"
else:
return "No"

print(check(N, A))

[実行結果(入力例1)]

No

与えられた3つの価格では1000円になることはないので、解はNoとなります。

[実行結果(入力例2)]

Yes

与えられた5つの価格のうち、[100, 400, 500]を選択すると1000円になりますので、解はYesとなります。

PyScript⑬(Bokeh)

Bokeh

PyScript内から、Bokehを使って折れ線グラフを表示します。

まずBokenライブラリを使うため、headタグに下記のようにCDN(コンテンツ配信ネットワーク)を設定します。

1
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-2.4.2.min.js"></script>

次にpy-configタグにbokehnumpyを指定します。

そしてpy-script内では、Bokeh関連ライブラリをいろいろとインポートし、その後でBokenライブラリを使って図形オブジェクトを作成します。

最後にjson.dumps関数Bokeh.embed.embed_item関数を使ってブラウザ上に折れ線グラフを表示します。

(ざっくりとした説明になってしまいましたが、詳しくは下記のサンプルコードをご参照ください🙇‍♂️)

[ソースコード]

bokeh.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<html>
<head>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-2.4.2.min.js"></script>

<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>

<body>
<div id="mpl"></div>

<py-config>
packages = ["bokeh", "numpy"]
</py-config>

<py-script>
import json
import pyodide

from js import Bokeh, console, JSON

from bokeh.embed import json_item
from bokeh.plotting import figure
from bokeh.resources import CDN

d = [0, 1, 4, 9, 16]
p = figure()
p.line(range(len(d)), d) # 第1引数がx軸、第2引数がy軸

p_json = json.dumps(json_item(p, "mpl")) # pは図形オブジェクトで、"mpl"はHtml上のDiv-IDを指定
Bokeh.embed.embed_item(JSON.parse(p_json))

</py-script>

</body>
</html>

[ブラウザ表示]

PyScript内で作成したBokehの図形オブジェクトを、ブラウザ上で表示することができました😊

PyScript⑫(Matplotlib)

Matplotlib

PyScript内から、Matplotlibを使って折れ線グラフを表示します。

まずpy-configタグにmatplotlibを指定し、PyScriptタグでmatplotlibライブラリをインポートします。

そしてpy-script内で、matplotlibライブラリを使って単純な折れ線グラフを描画し、最後にdisplay関数を使ってHtml上に折れ線グラフを表示します。

[ソースコード]

plotly1.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>

<body>
<div id="mpl"></div>

<py-config>
packages = ["matplotlib"]
</py-config>

<py-script>
import matplotlib.pyplot as plt
data = [2, 4, 6, 3, 5, 8, 4, 5]
plt.plot(data)
display(plt, target="mpl")
</py-script>

</body>
</html>

[ブラウザ表示]

PyScript内で作成したmatplotlibの図形オブジェクトを、ブラウザ上で表示することができました。

PyScript⑪(Plotlyで折れ線グラフ表示)

Plotlyで折れ線グラフ表示

PyScript内から、Plotlyを使って折れ線グラフを表示します。

まずpy-envタグにpandasplotlyを指定し、PyScript内からこの2つのライブラリをインポートします。

(Plotlyライブラリの中ではPandasライブラリを使用しています。)

次にpy-scriptタグ内では、折れ線グラフを表示するグラフオブジェクトを作成し、そのオブジェクトをjson.dump関数でjson化します。

その際、引数にはcls=plotly.utils.PlotlyJSONEncoderを指定するのがポイントです。

json化した図形オブジェクトは、JavaScript内のJSON.parse関数でオブジェクト化し、最後にPlotly.newPlotを使って描画します。

[ソースコード]

plotly1.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<html>
<head>

<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<script src='https://cdn.plot.ly/plotly-latest.min.js'></script>

<py-env>
- pandas
- plotly
</py-env>
</head>

<body>

<div id="chart1"></div>

<script type='text/javascript'>
function plot(graph, chart) {
var figure = JSON.parse(graph)
Plotly.newPlot(chart, figure);
}
</script>

<py-script>
import js, json
import pandas as pd
import plotly
import plotly.express as px

fig = px.line([[1, 1], [2, 5], [3, 4], [4, 8], [5, 2]])
graphJSON = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
js.plot(graphJSON, "chart1")
</py-script>

</body>
</html>

[ブラウザ表示]

PyScript内で作成したPlotlyのグラフオブジェクトを、ブラウザ上に表示することができました。

PyScript⑩(JavaScriptからPyScriptの関数を使う)

JavaScriptからPyScriptの関数を使う

PyodideのランタイムPyScript.runtime)を使うと、JavaScriptからPyScriptの変数だけでなく関数を使用することもできます。

例えばPythonのsort関数を使用するためには、JavaScript内でpyscript.runtime.globals.get('sorted')とし、取得した関数に並べ替えるデータ(配列)を渡します。

[ソースコード]

p2j.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>

<body>

<!-- クリックすると'Apple', 'Banana', 'Candy', 'Donut'を並び変えます。 -->
<button onclick="sortInPython(['Candy', 'Donut', 'Apple', 'Banana'])">Sort In Python And Log</button>
<script>
function sortInPython(data){
js_sorted = pyscript.runtime.globals.get('sorted') // Pythonのsort関数を取得
const sorted_data = js_sorted(data) // Pythonのsort関数を使って並び変え
for (const item of sorted_data){
console.log(item)
}
}
</script>

</body>
</html>

[ブラウザ表示]

ボタンをクリックすると、コンソール(開発ツール)に配列データが並び変えられて表示されることを確認できます。

JavaScriptからPyScriptsort関数を使うことができました。

PyScript⑨(JavaScriptからPyScriptの変数を参照)

JavaScriptからPyScriptの変数を参照

PyodideのランタイムPyScript.runtime)を使うと、JavaScriptからPyScriptの変数を参照することができます。

PyScript内のグローバル変数を参照するためにはJavaScriptpyscript.runtime.globals.get('変数名')というように記述します。

[ソースコード]

p2j.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>

<body>

<py-script>x = 42</py-script>

<button onclick="showX()">Click Me to Get 'x' from Python</button>
<script>
function showX(){
alert(`In Python right now, x = ${pyscript.runtime.globals.get('x')}`)
}
</script>

</body>
</html>

[ブラウザ表示]

JavaScriptからPyScriptの変数を参照することができました。

PyScript⑧(PyScriptからJavaScriptをコール)

PyScriptからJavaScriptをコール

PyScriptからは、JavaScriptの変数を読み込んだり、JavaScriptの関数をコールしたりすることができます。

PyScriptからJavaScriptの変数や関数をインポートするためにはfrom js import ...と宣言します。

単純なJavaScriptの変数は、同等のPythonのデータ型にコンバートされます。(暗黙的コンバート

より複雑なオブジェクトはJSProxyオブジェクトにラップされ、Pythonオブジェクトのように動作します。

[ソースコード]

j2p.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>

<script>
name = "Guido" // JavaScriptの変数
function addTwoNumbers(x, y){ // JavaScriptの関数
return x + y;
}
</script>

<body>
<py-script>
# PythonからJavaScriptの変数と関数をインポートする
from js import name, addTwoNumbers

print(f"Hello {name}")
print("Adding 1 and 2 in Javascript: " + str(addTwoNumbers(1, 2)))
</py-script>
</body>
</html>

[ブラウザ表示]

PyScriptからJavaScriptの変数を参照したり、JavaScriptの関数をコールしたりすることができました。