1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

蟻本をpythonで(chapter3 中級編~)

Last updated at Posted at 2020-09-07

#はじめに
プログラミングコンテストチャレンジブック(通称:蟻本)の問題をpythonで解いていきます。
類題も交えながら少しずつ更新していく予定です。

初級編については@sabaさんの記事を参照するのが良いと思います。
https://qiita.com/saba/items/affc94740aff117d2ca9

#3-1 値の探索だけじゃない!"二分探索"
###p128 lower_bound

N = 5
A = [2, 3, 3, 5, 6]
K = 3

# K以上であるAiのうち最も左にあるもののインデックス
print(bisect_left(A, K))

###p129 cable master

N = 4
K = 11
L = [8.02, 7.43, 4.57, 5.39]

def f(target):
    cnt = 0
    for i in range(N):
        cnt += math.floor(L[i] / target) #各紐から長さtargetの紐がいくつ取れるか
    if cnt >= K: #K本以上取れれば
        return True
    else:
        return False

ng = 0
ok = 10 ** 9 + 1
for i in range(100):
    mid = (ng + ok) / 2
    if f(mid):
        ng = mid
    else:
        ok = mid
print(math.floor(ok * 100) / 100) # 小数点2位まで求める

###p131 aggressive cows

N = 5
M = 3
X = [1, 2, 8, 4, 9]
X.sort()

def c(d):
    last = 0 # 1つ目の小屋を置く
    # M - 1回現在の場所からd以上離れた場所に小屋を置けるか
    for _ in range(M - 1):
        crt = last + 1 # crtを進めて次に小屋を置く場所を探す
        while crt < N and X[crt] - X[last] < d:
            crt += 1 

        if crt == N: # 次に小屋を置く場所が見つからなければ
            return False

        last = crt # 次の小屋を置く

    return True # M - 1回小屋を置ければ

ok = -1
ng = 10 ** 9 + 1

while abs(ok - ng) > 1:
    mid = (ok + ng) // 2
    if c(mid):
        ok = mid
    else:
        ng = mid
print(ok)

###p132 平均最大化

N = 3
K = 2
W = [2, 5, 2]
V = [2, 3, 1]

def c2(x, w, v):
    # 「単位重さあたりの価値がx以上になる」を満たすよう選んだ商品の集合を
    # S([w1, v1], [w2, v2]...[wk, vk])とする。この時
    # sum(v1, v2...vk) / sum(w1, w2...wk) >= x より
    # sum(v1, v2...vk) - x * sum(w1, w2...wk) >= 0
    # sum(v1 - x * w1, v2 - x * w2, ...vk - x * wk) >= 0

    # v[i] - x * w[i]を大きい順にk個取ったときそれが0以上になればいい
    cnt = 0
    items = [v[i] - x * w[i] for i in range(N)]
    items.sort(reverse = True)
    for i in range(K):
        cnt += items[i]
    return cnt >= 0

ok = 0
ng = 10 ** 9 + 1

for i in range(100):
    mid = (ng + ok) / 2
    if c2(mid, W, V):
        ok = mid
    else:
        ng = mid
print(ok)

#3-2 厳選!頻出テクニック(1)

p135 subsequence

N = 10
S = 15
A = [5, 1, 3, 5, 10, 7, 4, 9, 2, 8]
right = 0
total = 0
ans = float('inf')

for left in range(N):
    while right < N and total < S:
        # 条件を満たすまでrightを進める total >= Sになった時点で打ち切る

        # left, right, total = 0
        # total < SなのでA[0]をtotalに足してrightを1つ増やす
        # left, right, total = 0, 1, 5
        # ...
        # left, right, total = 0, 4, 14
        # total < SなのでA[4]をtotalに足して(この時点でtotal < Sになる)rightを1つ増やす
        # left, right, total = 0, 5, 24
        # total >= SなのでA[5]を足さない

        total += A[right]
        right += 1

    # もしtotal < Sのままrightが右端まで達したら
    if total < S:
        continue
 
    # left, right = 0, 5なら閉区間[0, 4]の総和がS以上になる
    # → 半開区間[0, 5)の総和がS以上になる
    ans = min(ans, right - left)

    if left == right:
        right += 1
    total -= A[left]

    # print(left, right)

print(ans)

p137 Jessica's Reading Problem

P = 5
A = [1, 8, 8, 8, 1]
dict = {}
for i in A:
    dict[i] = 0

l = 0
cnt = 0
ans = float('inf')
# 右を一つ進めて左をできる限り削る
for r in range(P):
    # 新しく一つ加える
    if dict[A[r]] == 0: # 新しい要素が来たなら
        cnt += 1
    dict[A[r]] += 1

    # 全ての要素を含みながら左を削っていく
    while True:
        # もし要素が1つしか無かったら削れない
        if dict[A[l]] == 1:
            break
        # 要素が2つ以上あれば削る
        dict[A[l]] -= 1
        l += 1

    if cnt < len(dict.items()):
        continue

    ans = min(ans, r - l + 1)
print(ans)

p138 Face The Right Way

N = 7
S = 'BBFBFBB'

def judge(k):
    imos = [0] * N
    # 最初の一回をひっくり返すかどうか
    if S[0] == 'B':
        imos[0] = 1
    # 前から順に牛が後ろを向いているならひっくり返す
    # k = 4なら
    # BBFBFBB
    # [  ]
    #  [  ]
    #   [  ]
    #    [  ]
    # 最大4回ひっくり返す
    for i in range(1, N - k + 1):
        if i < k:
            rev = imos[i - 1]
        else:
            rev = imos[i - 1] - imos[i - k]
        if (S[i] == 'B') ^ (rev % 2):
            imos[i] += 1
        imos[i] += imos[i - 1]

    # 残りの牛が前を向いているか調べる
    for i in range(N - k + 1, N):
        if i < k:
            rev = imos[N - k]
        else:
            rev = imos[N - k] - imos[i - k]
        if (S[i] == 'B') ^ (rev % 2):
            return float('inf')

    return imos[N - k]

K = 0
M = float('inf')
for i in range(1, N + 1):
    opt = judge(i)
    if opt < M:
        M = opt
        K = i
print(K, M)

p141 Fliptile

M = 4
N = 4
maze = [
[1, 0, 0, 1],
[0, 1, 1, 0],
[0, 1, 1, 0],
[1, 0, 0, 1]
]

def changer(o, f):
    for i in range(1, M):
        for j in range(N):
            # 一つ上のmazeとflipを調べる
            # mazeが黒 and flipが偶数回ひっくりかえる or mazeが白 and flipが奇数回ひっくりかえるなら
            if (maze[i - 1][j] == 1) ^ (f[i - 1][j] % 2):
                o[i][j] += 1
                f[i][j] += 1
                f[i - 1][j] += 1
                if j >= 1:
                    f[i][j - 1] += 1
                if j < N - 1:
                    f[i][j + 1] += 1
                if i < M - 1:
                    f[i + 1][j] += 1

    return o, f

# 1行目について全探索
# 1行目が定まると2行目以降が一意に定まる
# 1行目について全探索:O(2 ** N) 2行目以降を埋めていく:O(MN)かかるので
# 合計の計算量はO(MN(2 ** N))になる
for bit in range(1 << N):
    opt = [[0] * N for i in range(M)]
    flip = [[0] * N for i in range(M)]
    for i in range(N):
        if bit & (1 << i):
            opt[0][i] += 1
            flip[0][i] += 1
            if i >= 1:
                flip[0][i - 1] += 1
            if i < N - 1:
                flip[0][i + 1] += 1

    opt, flip = changer(opt, flip)

    # 最後の列について判定
    for j in range(N):
        # 違うなら
        if (maze[M - 1][j] == 1) ^ (flip[M - 1][j] % 2):
            break
    else:
        for i in opt:
            print(*i)

p145 physics experiment

# antsと同じく玉同士はすり抜けるものとして考える
N, H, R, T = 2, 10, 10, 100
g = 10

def judge(time):
    if time < 0:
        return H
    t = math.sqrt(2 * H / 10)
    k = int(time / t)
    if k % 2 == 0:
        d = time - k * t
        return H - g * d * d / 2
    else:
        d = k * t + t - time
        return H - g * d * d / 2

ans = []
for i in range(N):
    # 一秒ごとにボールを落下させる
    ans.append(judge(T - i))
# ボールは互いにすり抜けるものと考えて良い
ans.sort()
for i in range(N):
    # RはセンチメートルだがHはメートルなのに注意
    print(ans[i] + (2 * R * i / 100))

p147 4 values whose sum is 0


# 二分探索
def binary_search_loop(data, target):
    imin = 0
    imax = len(data) - 1
    while imin <= imax:
        imid = imin + (imax - imin) // 2
        if target == data[imid]:
            return imid
        elif target < data[imid]:
            imax = imid - 1
        else:
            imin = imid + 1
    return False

N = 6
A = [-45, -41, -36, -36, 26, -32]
B = [22, -27, 53, 30, -38, -54]
C = [42, 56, -37, 75, -10, -6]
D = [-16, 30, 77, -46, 62, 45]

re_A = []
re_C = []
# Ai + Bj
for i in range(N):
    for j in range(N):
        re_A.append(A[i] + B[j])
re_A.sort()
# Ci + Dj
for i in range(N):
    for j in range(N):
        re_C.append(C[i] + D[j])
re_C.sort()

ans = 0
for i in re_A:
    # ind1:合計で0以上になるもののうち一番左のインデックス
    # ind2:合計で1以上になるもののうち一番左のインデックス
    #             ind1       ind2
    # [...-2, -1, (0), 0, 0, (2), 2, 3]
    ind1 = bisect_left(re_C, 0 - i)
    ind2 = bisect_left(re_C, 0 - i + 1)
    ans += ind2 - ind1
print(ans)

p148 巨大ナップサック

N = 4
w = [2, 1, 3, 2]
v = [3, 2, 4, 2]
W = 5

# 半分全列挙 + 二分探索
def re_list(weight, value):
    fore_list = []
    # まず全通り組み合わせる
    for bit in range(1 << len(weight)):
        wei = 0
        val = 0
        for i in range(len(weight)):
            if bit & (1 << i):
                wei += weight[i]
                val += value[i]
        fore_list.append([wei, val])
    fore_list.sort()

    # リスト再作成
    # 明らかにいらないものは消している
    # ABC032 D - ナップサック問題の解説参照
    alta_w = []
    alta_v = []
    now = -1
    for i in fore_list:
        if now < i[1]:
            now = i[1]
            alta_w.append(i[0])
            alta_v.append(i[1])
    return alta_w, alta_v

def half_knapsack(N, limit, weight, value):
    # 半分全列挙
    fore_w, fore_v = re_list(weight[:N // 2], value[:N // 2])
    back_w, back_v = re_list(weight[N // 2:], value[N // 2:])

    ans = 0
    for b_w, b_v in zip(back_w, back_v):
        if b_w > limit:
            continue

        opt = b_v
        index = bisect_right(fore_w, limit - b_w)
        if index > 0:
            opt += fore_v[index - 1]
        ans = max(ans, opt)

    return ans
print(half_knapsack(N, W, w, v))

###p150 領域の個数

# 与えられたx, yの上限が小さいため今回の入力例では全く圧縮されないんです
W, H, N = 10, 10, 5
# (x1, y1)から(x2, y2)まで線を引く
X1 = [i - 1 for i in [1, 1, 4, 9, 10]]
X2 = [i - 1 for i in [6, 10, 4, 9, 10]]
Y1 = [i - 1 for i in [4, 8, 1, 1, 6]]
Y2 = [i - 1 for i in [4, 8, 10, 5, 10]]

# 縦
row = set()
r_index = {}
# 横
col = set()
c_index = {}
# 変換
for x, y in zip(X1 + X2, Y1 + Y2):
    # どの横列が必要か
    row.add(y)
    if y > 0:
        row.add(y - 1)
    if y < H - 1:
        row.add(y + 1)

    # どの縦列が必要か
    col.add(x)
    if x > 0:
        col.add(x - 1)
    if x < W - 1:
        col.add(x + 1)

# 圧縮後のどの座標になるか
H = len(row)
row = sorted(list(row))
for i in range(H):
    r_index[row[i]] = i

W = len(col)
col = sorted(list(col))
for i in range(W):
    c_index[col[i]] = i

X1 = [c_index[i] for i in X1]
X2 = [c_index[i] for i in X2]
Y1 = [r_index[i] for i in Y1]
Y2 = [r_index[i] for i in Y2]

# print(X1, Y1, X2, Y2)
# X1: [0, 0, 3, 8, 9]
# Y1: [3, 7, 0, 0, 5]

# X2: [5, 9, 3, 8, 9]
# Y2: [3, 7, 9, 4, 9]

# あとは普通に解く
maze = [[0] * W for i in range(H)]
# 線を引く
for x1, y1, x2, y2 in zip(X1, Y1, X2, Y2):
    # 縦線
    if x1 == x2:
        if y1 > y2:
            y1, y2 = y2, y1
        for i in range(y1, y2 + 1):
            maze[i][x1] = 1
    # 横線
    else:
        if x1 > x2:
            x1, x2 = x2, x1
        for i in range(x1, x2 + 1):
            maze[y1][i] = 1

# p35 lake counting する
dx = [0, 1, 0, -1]
dy = [1, 0, -1, 0]

def dfs(y, x):
    maze[y][x] = 1
    for i in range(4):
        ny = y + dy[i]
        nx = x + dx[i]
        if 0 <= ny < H and 0 <= nx < W and maze[ny][nx] == 0:
            dfs(ny, nx)

ans = 0
for y in range(H):
    for x in range(W):
        if maze[y][x] == 0:
            dfs(y, x)
            ans += 1
print(ans)

3-3 さまざまなデータ構造を知ろう

1
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?