グラフ(辞書型編)

グラフ(辞書型編)

グラフ は、対象物の関係性 を表します。

グラフ を使うと、友人関係・鉄道路線・タスクの依存関係など、さまざまな関係性を表現できます。

関係性を グラフ で表すと、問題を抽象的に考えることができるというメリットがあります。

問題

次のような問題があります。

[問題]

頂点数がN個、辺数がM本であるような無向グラフ(辺の向きがないグラフ)があります。

頂点は隣接する頂点の情報(複数)を持っています。

それぞれの頂点は色の情報をもっており、色の情報は塗り替えることができます。

クエリ―は2種類あり下記のような操作を行います。

 クエリ―1:1 aという形式で情報が与えられ、頂点aに隣接する頂点 の色が 頂点aの色 に塗り変えられます。
 
 クエリ―2:2 a bという形式で情報が与えられ、頂点a の色が 色b に塗り替えられます。

[条件]

・頂点数Nは1以上200以下の整数

・変数MはN(N-1)/2以下の整数

・クエリ―数Qは1以上200以下の整数

解法・ソースコード

頂点と辺、色の情報を辞書型の変数で管理します。

辞書型のキーには 頂点のID を設定し、辞書型の値として 色情報と隣接する頂点 の情報を辞書型データとして設定します。

クエリ―ごとに各頂点の色情報を更新し、最後に各頂点の色情報を表示しています。

[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
26
27
28
29
30
31
32
33
34
35
36
37
38
#---------- 入力例 ----------
# ノード情報
nodes = {
1:{'color':5, # 色情報(初期)
'edge':[2]}, # 隣接ノード(1→2)
2:{'color':10, # 色情報(初期)
'edge':[1,3]}, # 隣接ノード(2→1、2→3)
3:{'color':15, # 色情報(初期)
'edge':[2]}, # 隣接ノード(3→2)
}
# クエリ―情報
querys = [(1, 2),
(2, 1, 20),
(1, 1)]
#----------------------------
# 各ノードの色情報を表示する
def show_color():
for k,v in nodes.items():
print(' node[{}]: color[{}]'.format(k, v['color']))

print('【初期のカラー情報】')
show_color()
print(' -------')

for query in querys:
print('クエリ―', query)
node = nodes[query[1]] # ノードを選択
if query[0] == 1: # クエリ―がタイプ1の場合
for e in node['edge']: # 選択ノードに隣接する各ノードの色を更新
nodes[e]['color'] = node['color']
else: # クエリ―がタイプ2の場合
node['color'] = query[2] # 選択ノードの色を更新
print(' (カラー情報)')
show_color()
print(' -------')

print('【解:カラー情報】')
show_color()

[実行結果]

【初期のカラー情報】
  node[1]: color[5]
  node[2]: color[10]
  node[3]: color[15]
 -------
クエリ― (1, 2)
 (カラー情報)
  node[1]: color[10]
  node[2]: color[10]
  node[3]: color[10]
 -------
クエリ― (2, 1, 20)
 (カラー情報)
  node[1]: color[20]
  node[2]: color[10]
  node[3]: color[10]
 -------
クエリ― (1, 1)
 (カラー情報)
  node[1]: color[20]
  node[2]: color[20]
  node[3]: color[10]
 -------
【解:カラー情報】
  node[1]: color[20]
  node[2]: color[20]
  node[3]: color[10]

1番目のクエリ―により、頂点2に隣接する全ての頂点の色が10に塗り替えられます。

2番目のクエリ―により、頂点1の色が色20に塗り替えられます。

3番目のクエリ―により、頂点1に隣接する全ての色が20に塗り替えられます。

最終的には、頂点1の色が20、頂点2の色が20、頂点3の色が10になっていることが確認できます。

バケット(辞書型編)

バケット(辞書型編)

前回は、配列 を使ってバケットの手法を実現しましたか、今回は 辞書型 を使ってバケットの手法を実現してみます。

問題は前回と同様のものを使います。

[問題]

N個 の正の整数があります。

この中に含まれる数値の種類の個数を求めて下さい。

[条件]

・各数字は、1以上10以下の整数

解法・ソースコード

前回は配列のインデックスと入力の数字を対応させました。

辞書型では、入力の数字を 辞書型のキー とし、数字の個数を 辞書型のバリュー(値) としてみます。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
12
13
#---------- 入力例 ----------
lst = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9]
#----------------------------
bucket = {} # バケットを辞書型として定義
# 入力リストからバケットを作成
for l in lst:
if l in bucket: # バケットにすでにキーがある場合
bucket[l] += 1 # カウントアップ
else: # バケットにまだキーがない場合
bucket[l] = 1 # 1つ目としてバケット(辞書型)に登録

print('バケット:', bucket)
print('解(種類の個数):', len(bucket))

前回配列を使ったときとコード量はあまり変わりませんが、配列では固定のエリア(数字の種類分の配列)を確保する必要があり未使用になる可能性もあったので、辞書型の方が無駄なく実装できているような気がします。

バケット: {3: 2, 1: 2, 4: 1, 5: 3, 9: 2, 2: 1, 6: 1, 8: 1}

解(種類の個数): 8

解は 8 となり、前回記事と同様に入力の数字の種類が 8種類 であることが確認できました。

バケット(配列編)

バケット(配列編)

バケット はあらかじめデータがとりうる値を、すべての容器(バケット)に順番どおりに並べ用意しておくアルゴリズムです。

バケット を使うと次のようなさまざまな問題に簡単に解を出すことができます。

  • 配列にある値が含まれるかどうかを判定できる。
  • 配列に含まれる値が何種類あるかわかる。
  • 配列の要素がすべて異なるかどうかを判定できる。

[問題]

N個の正の整数があります。

この中に含まれる数値の種類の個数を求めて下さい。

[条件]

・各数字は、1以上10以下の整数

解法・ソースコード

数字の範囲は10以下なので、サイズが11の配列を準備します。(配列のインデックスが0から10の整数を表す)

次に入力の数字ごとに配列に1を設定していきます。(その数字が存在したことを表す)

最終的にこの配列の合計値が、数値の種類の個数を表すことになります。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
12
#---------- 入力例 ----------
lst = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9]
#----------------------------
# バケット(全体を0で初初期化)範囲 0~10
bucket = [0] * 11

# 入力リストからバケットを作成
for l in lst:
bucket[l] = 1

print('バケット:', bucket)
print('解(数値の種類):', sum(bucket))

[実行結果]

バケット: [0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0]

解(種類の個数): 8

解は 8 となり、入力の数字の種類が 8種類 であることが確認できました。

二次元配列でマップ問題を解く

二次元配列でマップ問題を解く

次のような問題を考えます。

[問題]

長方形状のマップデータ(文字列)が与えられます。

1マスのデータは'#'が爆弾を表し、'.'は何もない空間(空きマス)を表します。

空きマスの上下左右および斜めの8方向に隣接しているマスの中に

爆弾のあるマスが何個あるかを調べて、その数を空きマス'.'に置き換えて

長方形状のマップを出力してください。

[条件]

・マップの縦幅と横幅は1以上かつ50以下です。

・1マスのデータは'#'(爆弾)または'.'(何もない空間)のみです。

解法・ソースコード

長方形のマップを表すために、str型の変数からなるlistを活用します。

長方形状のマップを上からy番目、左からx番目のマスを(x, y)と表します。

入力の文字列データを1マスずつの2次元配列(2次元リスト)に設定します。(14行目)

この問題では、各空きマス(x, y)に対して、その周囲8マスにある爆弾の個数を数えます。

マス(x, y)の周囲8マスは (x+1, y)、(x-1, y)、(x, y+1)、(x, y-1)、(x+1, y+1)、(x+1, y-1)、(x-1, y+1)、(x-1, y-1) と表すことができます。

これらのマスを調べるために、周囲8方向に隣接するマスとの座標の差分値を表す配列を用意しておくと便利です。(17行目)

[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
26
27
28
29
30
31
32
33
#---------- 入力例 ----------
# .が空間、#が爆弾
mp = '''........
..#...#.
........
....#...
.....#..'''
#----------------------------
print('【 元のマップ 】 ')
print(mp)
print()

# 入力データを二次元配列に設定
res = [list(line) for line in mp.split('\n')]

# 隣接8方向への相対位置を定義
dxy = [(1, 0), (-1, 0), (0, 1), (0, -1), (1, 1), (1, -1), (-1, 1), (-1, -1)]

for y, row in enumerate(res): # 縦ループ
for x, s in enumerate(row): # 横ループ
if s == '.': # 空きマスの場合
res[y][x] = 0 # 初期化(周囲の爆弾数)
for dx, dy in dxy: # 8方向をループ
x1, y1 = x + dx, y + dy # マスの周囲をx1,y1とする
# マップ外の場合はパス
if x1 < 0 or x1 > len(row) - 1 or y1 < 0 or y1 > len(res) - 1:
pass
elif res[y1][x1] == '#': # 周囲が爆弾'#'の場合
res[y][x] += 1 # 爆弾数カウントアップ

print('【 隣接する爆弾の個数を表示したマップ 】 ')
for row in res:
print(*row, sep='')

[実行結果]

【 元のマップ 】 
........
..#...#.
........
....#...
.....#..

【 隣接する爆弾の個数を表示したマップ 】 
01110111
01#101#1
01121211
0001#210
00012#10

空きマスの周辺にある爆弾数をカウントしてマップを表示することができました。

全探索

全探索

次のような問題を考えます。

[問題]

500円玉がA枚、100円玉がB枚、50円玉がC枚あります。

これらの硬貨の中から何枚かを選び、合計金額をちょうどX円にする方法は

何通りありますか。

[条件]

・それぞれの枚数A、B、Cは0以上かつ50以下の整数。
・目標金額Xは50以上かつ20000以下の50の倍数。
・それぞれの枚数の合計(A+B+C)は1以上。

解法・ソースコード

この問題は、各硬貨の枚数分それぞれループを回して目標の金額になるかどうかをチェックすることで解くことができます。(三重ループしながら金額をチェック)

硬貨は 0枚 の時があるので、ループ回数は各硬貨の 枚数分 + 1 となることに注意してください。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#---------- 入力例 ----------
a = 2 # 500円玉の枚数
b = 2 # 100円玉の枚数
c = 2 # 50円玉の枚数
x = 100 # 目標金額
#----------------------------
res = 0 # 何通りあるかという解
# 全探索
for a1 in range(0, a + 1): # 500円玉の枚数分ループ
for b1 in range(0, b + 1): # 100円玉の枚数分ループ
for c1 in range(0, c + 1): # 50円玉の枚数分ループ
# 合計金額がX円に一致した場合
if 500 * a1 + 100 * b1 + 50 * c1 == x:
# それぞれの硬貨の枚数を出力
print(f'500円{a1}枚、100円{b1}枚、50円{c1}枚')
res += 1 # 解をカウントアップ
print('解:', res)

[実行結果]

500円0枚、100円0枚、50円2枚
500円0枚、100円1枚、50円0枚
解: 2

解は 2通り あることが分かりました。

硬貨の組み合わせは、合計金額が一致した場合のそれぞれの硬貨数を表示することで確認できます。

反転

反転問題

次のような問題があります。

[問題]

N頭の牛が1列に並んでいて、それぞれの牛は前か後ろのいずれかを向いています。

農夫は全ての牛を前向きにするために、牛を回転する機械を買うことにしました。

この機会は購入時に 数K を設定する必要があり、1回の操作で K頭の連続する

牛 の向きを反転させることができます。

全ての牛を前向きにするために必要な最小の 操作回数M と、それを達成する

最小のK を求めてください。

解法・ソースコード

まず、一番左端の牛に注目します。この牛を含む区間は1つしかありません。

したがって、この牛が前を向いているならば、その区間は反転させる必要がありません。

逆に、この牛が後ろを向いているならば、その区間は反転させる必要があります。

そして、これ以降はこの1番左の区間は考える必要がなくなります。

これを繰り返し行うことによって、必要な最小の反転回数を求めることができます。

また、計算量 を落とすためにリストfを定義し、区間を反転させた場合は1そうでない場合は0を設定し、その合計値が奇数である場合は最初の向きとは逆に、そうでなければ最初の向きのままとなっていることを確認しています。

[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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#---------- 入力例 ----------
cows = 'BBFBFBB' # F:前向き、B:後ろ向き
#----------------------------
n = len(cows) # 牛の頭数
k = 1 # 求める最小の牛を回転させる連続頭数(初期化)
m = n # 求める最小の操作回数(初期化)

# 牛を回転させる連続頭数kを固定した時の最小操作回数を求める
# 解が存在しない場合は -1 を返す
def calc(k):
# 区間[i, i + k - 1 ]を回転させたかどうか
f = [0 for _ in range(n)]
res = 0 # 操作回数
total = 0 # リストfの和
for i in range(n - k + 1):
if (dir[i] + total) % 2 != 0: # 先頭の牛が後ろを向いている場合
# 回転操作を行う
res += 1
f[i] = 1
total += f[i]
if i - k + 1 >= 0:
total -= f[i - k + 1]
# 残りの牛が前を向いているかどうかチェック
for i in range(n - k + 1, n):
if (dir[i] + total) % 2 != 0:
return -1
if i - k + 1 >= 0:
total -= f[i - k + 1]
return res

# 牛の方向を0(F:前向き),1(B:後ろ向き)に変換
dir = [0 if x == 'F' else 1 for x in list(cows)]

for i in range(1, n + 1): # 牛の頭数分ループする
print(i, '頭回転させる機械の場合')
x = calc(i)
if x >= 0 and m > x: # 牛を全部前向きにでき、かつ操作回数がより少ない場合
print(x, '回操作で牛を全部前向きにできる。')
m = x # 最小の操作回数を更新
k = i # 回転させる最小の連続頭数を更新
else:
print('牛の向きを全部前向きにするとはできない。')
print()

print(f'解:回転させる最小の連続頭数={k} 最小の操作回数={m}')

[実行結果]

1 頭回転させる機械の場合
5 回操作で牛を全部前向きにできる。

2 頭回転させる機械の場合
牛の向きを全部前向きにするとはできない。

3 頭回転させる機械の場合
3 回操作で牛を全部前向きにできる。

4 頭回転させる機械の場合
牛の向きを全部前向きにするとはできない。

5 頭回転させる機械の場合
牛の向きを全部前向きにするとはできない。

6 頭回転させる機械の場合
牛の向きを全部前向きにするとはできない。

7 頭回転させる機械の場合
牛の向きを全部前向きにするとはできない。

解:回転させる最小の連続頭数=3 最小の操作回数=3

牛を連続で 3頭回す機械<.b> を購入すれば、最小3回の操作</ > で牛を全部前向きにできることが分かりました。

座標圧縮

座標圧縮

座標圧縮 というアルゴリズムを使うと、効率的に問題を解くことができるケースがあります。

[問題]

2次元のマップがあり、何もない通り抜けのできる空間(.)と通り抜けのできない壁(#)が設定されています。

壁によって分断されている領域は何個あるでしょうか。

解法・ソースコード

この問題では、前後の行または列に 変化がない場合 その行または列を省いても、壁によって分断される 領域の個数 は変わりません。

まず、同じ行があったらその行を削除し、次に同じ列があったらその列を削除するという処理を行います。

[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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#---------- 入力例 ----------
# .が空間、#が壁
mp = '''...#....#.
...#....#.
...#....#.
######..#.
...#....#.
...#.....#
...#.....#
##########
...#.....#
...#.....#'''
#----------------------------

mp_compress = {} # 縦と横を圧縮したデータ

print(' 【 圧縮前 】 ')
print(mp)
print()

# 縦を圧縮
lst = [] # 縦を圧縮したデータ
pre = '' # 1行前のデータ(初期化)
for line in mp.split():
if pre != line:
lst.append(line)
pre = line
# 横を圧縮
pre = '' # 1列前のデータ(初期化)
w = 0
for col in range(len(lst[0])): # 列ごとにループ
row = [l[col] for l in lst] # 1列分のデータを取得
line = ''.join(row)
if pre != line:
for y, s in enumerate(list(line)):
mp_compress[w, y] = s
w += 1
pre = line

print(' 【 圧縮後 】 ')
for y in range(len(lst)):
for x in range(w):
print(mp_compress[x,y], end='')
print()

[実行結果]

 【 圧縮前 】
...#....#.
...#....#.
...#....#.
######..#.
...#....#.
...#.....#
...#.....#
##########
...#.....#
...#.....#

 【 圧縮後 】
.#..#.
###.#.
.#..#.
.#...#
######
.#...#

問題なく座標が圧縮され、壁に分断されている領域の数も変わっていないことが確認できました。

次に、圧縮されたマップに対して分断されている領域のカウントを行います。

これは、まず壁ではない地点をみつけ、そこからつながっている部分を 壁(#) に置き換えるという操作を繰り返します。

一回の深さ優先探索で、始めの壁ではない地点からつながっている空間(.)が全て壁(#)に変わるので、深さ優先探索を行った回数 が解となります。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 深さ優先探索(Depth-First Search)
def dfs(x, y):
global mp_compress
# 今いるところを#に置き換え
mp_compress[x, y] = '#'
for dx, dy in [(1,0), (-1,0), (0,1), (0,-1)]:
pos = (x + dx, y + dy)
if pos in mp_compress and mp_compress[pos] == '.':
dfs(pos[0], pos[1])
# 領域を数える
cnt = 0
for pos,v in mp_compress.items():
if v == '.':
dfs(pos[0], pos[1])
cnt += 1
print('解:', cnt)

[実行結果]

解: 6

解は 6 となり、壁により分断されている空間が6つであることが確認できました。

二分探索④:平均最大化(全4回)

平均最大化

二分探索 を使って、平均最大化問題 を解いてみます。

問題

重さと価値がわかっている品物が複数あります。

この中から、k個選んだ時の重さあたりの価値の最大値を求めてください。

ソースコード

この問題は 重さあたりの価値がx以上となるように選ぶことができるかどうか という関数を定義し、二分探索で最大のxを求める ことで解くことができます。

関数としては、それぞれの品物に対して(価値 - 重さあたりの価値[x] × 重さ)を計算し、この結果の大きいほうからk個の和を求めて、それが0以上かどうかで判定します。

[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
#------ 入力例 ------
k = 2 # 選択する個数
# それぞれの品物の重さと価値
items = [{'weight':2, 'value':2},
{'weight':5, 'value':3},
{'weight':2, 'value':1}]
#--------------------
# 重さあたりの価値[x]が実現可能かどうか
def check(x):
# それぞれの品物に対して、(価値 - 重さあたりの価値[x] × 重さ)を計算する
res = [item['value'] - x * item['weight'] for item in items]
res = sorted(res, reverse=True) # 大きいほうからソート
return sum(res[:k]) >= 0 # 大きいほうからk個の和が0以上かどうか

# 解の存在範囲を初期化
lb, ub = 0, 999
for _ in range(100): # 十分な回数繰り返す
mid = (lb + ub) / 2 # 解の存在範囲の中央値
if check(mid): # 中央値が実現可能かどうか
lb = mid
else:
ub = mid
print('解:', lb)

[実行結果]

解: 0.75

解は 0.75 となりました。

品物の価値と重さを確認すると、1つめと3つめの品物を選んで重さあたりの価値を計算すると (2 + 1) / (2 + 2) = 0.75 となり 最大値 となっていることが確認できます。

二分探索③:最小値の最大化(全4回)

最小値の最大化

前回に引き続き、二分探索 を使って 最小値の最大化 を求める問題を解いてみます。

問題

おじいさんがN個の小屋を作りました。小屋は直線状に並んでいます。

おじいさんは馬をM頭飼っていますが、それぞれの馬ができるだけ離れるように

小屋にいれたいと考えています。

最も近い2頭の馬の間隔を 最大化 するためには馬をどのように

小屋に入れたらよいでしょうか。

ソースコード

この問題は、ある条件を満たす最大値を求める問題です。

下記のような手順で問題を解いていきます。

  1. 小屋の位置を昇順にソートする。
  2. 最初の馬を1番目の小屋に入れる。
  3. 次の馬を2番目の小屋にいれて、ある間隔を確保できるかどうかチェックする。
  4. もし間隔があけられなければ、次の小屋に移動し再び間隔のチェックを行う。
  5. ある間隔を確保できたら、(間隔を確保できた)小屋の位置を基準にして次の馬を入れる小屋の位置を同じようにチェックする。
  6. 途中で小屋がなくなったら、条件を満たすことができなかったと判定する。
  7. 最後の馬まで小屋にいれることができたら条件を満たせたと判定する。
  8. 2~7の判定を二部探索を使って、解の存在範囲(馬同士の間隔)を狭めながらチェックする。

[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
26
27
28
29
30
31
32
33
#------ 入力例 ------
m = 3 # 馬の頭数
lst = [1, 2, 8, 4, 9] # 小屋の位置
#--------------------
n = len(lst) # 小屋の数

# 条件を満たす距離かどうかを判定
def check(d):
last = 0 # 小屋の位置①
for _ in range(m - 1):# 最初の馬は1番目の小屋に入れるので(馬の頭数 - 1頭)分ループする
crt = last + 1 # 小屋の位置②
# 小屋の位置が存在し かつ
# 小屋の位置①と小屋の位置②がある距離[d]未満だったら
while crt < n and lst[crt] - lst[last] < d:
crt += 1 # 小屋の位置2を後ろにずらす(距離[d]を確保するため)
if crt == n: # 最後の小屋の位置をこえたら
return False # 条件を満たさない
last = crt # 条件を満たしたので、小屋の位置①を小屋の位置②に移動
return True # すべての馬を小屋にいれられたので条件を満たす

lst = sorted(lst) # 小屋の位置を昇順にソート

# 解の存在範囲を初期化
lb = 0
ub = 9999
# 条件を満たすかどうか解の存在範囲を狭めながらチェックする
while ub - lb > 1:
mid = (lb + ub) // 2
if check(mid):
lb = mid
else:
ub = mid
print('解:', lb)

[実行結果]

解: 3

馬の間隔を3ずつあければうまく馬をいれられることが分かりました。

つまり 位置1,4,9 の小屋に馬を格納すれば、最も近い2頭の馬の 間隔を最大化 することができることになります。

二分探索②:解を仮定して判定(全4回)

解を仮定して判定(最大値)

二分探索 を使うと、ある条件を満たす最大値を求めるときに非常に有用です。

問題

N本の紐 があり長さはそれぞれ異なります。

これらの紐を切って、同じ長さの紐をK本 作るときの 最長の長さ を求めなさい。

答えは小数点以下2桁までを切り捨ててください。

ソースコード

区間の 下端lbを条件満たす値 に、上端ubを条件を満たさない値 にそれぞれ初期化し、解の存在範囲(lb、ub)が十分小さくなるまで、中央の値が条件を満たすかどうか判定し存在範囲を狭めていきます。

最終的に、lbが求めたい 最大の値 となります。

[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
#------ 入力例 ------
lst = [8.12, 7.53, 4.47, 5.29] # 各紐のながさ
k = 11 # 作りたい同じ長さの紐の本数
#--------------------
n = len(lst) # 紐の本数

# 長さxの紐をk本作れるかどうか
def check(x):
cnt = sum([int(l / x) for l in lst])
return cnt >= k

lb = 0 # 初期化(下限の範囲)
ub = 999999 # 初期化(上限の範囲)

# 解の存在範囲が十分狭くなるまで繰り返す
for _ in range(99):
mid = (lb + ub) / 2
if check(mid):
lb = mid
else:
ub = mid

# 小数点第2までを切り捨て
print(('解: {:.2f}').format(lb * 100 // 100))

[実行結果]

解: 2.00

長さが2 であれば、11本の同じ長さの紐 が作れることを導き出すことができました。

解を仮定して判定(最小値)

上記の処理と同様にある条件を満たす 最小値 を求めることもできます。

区間の 下端lbを条件満たさない値 に、上端ubを条件を満たす値 にそれぞれ初期化し、解の存在範囲(lb、ub)が十分小さくなるまで、中央の値が条件を満たすかどうか判定し存在範囲を狭めていきます。

最終的に、ub が求めたい 最小の値 となります。

© 2026 Playing with Python All Rights Reserved.
Theme by hipaper