mean_abs_error’s blog

ソフトウェアとシステムの狭間

【AtCoder】PythonでABC325を解く

A問題: Takahashi san

atcoder.jp

問題の概要

与えられた名字と名前に対して、名字の方に敬称(san)を付けよ。

問題の考察

Pythonで文字列のフォーマットをいじるのに便利なf-stringを使って敬称を付けます。

コード

def main():
    S, T = input().split()
    ans = f"{S} san"
    print(ans)


if __name__ == "__main__":
    main()

B問題: World Meeting

atcoder.jp

問題の概要

ある国の拠点 nの時刻は世界標準時で0時のときに X_n時で、その拠点には  W_n人の社員が所属している。各拠点の社員は、現地時間で09:00-18:00の時間帯のときのみ会議に参加できる。適切な開始時刻を選んで1時間の会議を設定するとき、会議の参加人数は最大で何人になるか。

問題の考察

世界標準時について0時から23時までを全探索します。ある世界標準時における参加人数は、その世界標準時を拠点 nの時間に直したときに9以上18未満を満たす W_nの総和となります。

コード

def solve(N, W, X):
    ans = 0
    for t in range(24):
        total = sum(w for w, x in zip(W, X) if 9 <= (t + x) % 24 < 18)
        ans = max(ans, total)

    return ans


def main():
    N = int(input())
    W = []
    X = []
    for _ in range(N):
        w, x = map(int, input().split())
        W.append(w)
        X.append(x)
    ans = solve(N, W, X)
    print(ans)


if __name__ == "__main__":
    main()

C問題: Sensors

atcoder.jp

問題の概要

 H \times Wのマス目にセンサが配置されている。上下左右斜めで隣接しているセンサ同士は、連動して動作する一纏まりのセンサ群として見なせる。センサ群は何個あるか。

問題の考察

幅優先探索やUnionFindで解くことができますが、ここではUnionFindで解きます。上下左右斜めで隣接しているセンサをUnionFindで同じ集合として結合し、最後に各集合の親がセンサか否かでセンサ群を数えます。

UnionFindのソースコードは、こちらから引用しました(https://note.nkmk.me/python-union-find/)。いつもお世話になっております。

コード

from collections import defaultdict


class UnionFind:
    def __init__(self, n):
        self.n = n
        self.parents = [-1] * n

    def find(self, x):
        if self.parents[x] < 0:
            return x
        else:
            self.parents[x] = self.find(self.parents[x])
            return self.parents[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)

        if x == y:
            return

        if self.parents[x] > self.parents[y]:
            x, y = y, x

        self.parents[x] += self.parents[y]
        self.parents[y] = x

    def size(self, x):
        return -self.parents[self.find(x)]

    def same(self, x, y):
        return self.find(x) == self.find(y)

    def members(self, x):
        root = self.find(x)
        return [i for i in range(self.n) if self.find(i) == root]

    def roots(self):
        return [i for i, x in enumerate(self.parents) if x < 0]

    def group_count(self):
        return len(self.roots())

    def all_group_members(self):
        group_members = defaultdict(list)
        for member in range(self.n):
            group_members[self.find(member)].append(member)
        return group_members

    def __str__(self):
        return "\n".join(f"{r}: {m}" for r, m in self.all_group_members().items())


def solve(H, W, S):
    uf = UnionFind((H + 2) * (W + 2))
    for h in range(1, H + 1):
        for w in range(1, W + 1):
            if S[h][w] == "#":
                u = h * (W + 2) + w
                for dh in range(-1, 2):
                    for dw in range(-1, 2):
                        hh = h + dh
                        ww = w + dw
                        if S[hh][ww] == "#":
                            v = hh * (W + 2) + ww
                            uf.union(u, v)

    ans = 0
    for r in uf.roots():
        h = r // (W + 2)
        w = r % (W + 2)
        ans += int(S[h][w] == "#")

    return ans


def main():
    H, W = map(int, input().split())
    S = []
    S.append("." * (W + 2))
    for _ in range(H):
        line = "." + input() + "."
        S.append(line)
    S.append("." * (W + 2))
    ans = solve(H, W, S)
    print(ans)


if __name__ == "__main__":
    main()

D問題: Printing Machine

atcoder.jp

問題の概要

商品 nは、今から T_n秒後に印字機に入り、その D_n秒後に印字機から出る。印字機は、中にある商品に対して一瞬で印字することができるが、印字の度に1秒間のチャージ時間を必要とする。印字する対象の商品と印字のタイミングを適切に選んだとき、最大で何個の商品に印字できるか。

問題の考察

印字機に入っている商品の中で最も出が早いものから印字する貪欲法でよさそうですが、 T_n, D_n が大きいため、印字機への入りと出を毎秒シミュレーションする方法だと計算量的に間に合いません。

時間でのシミュレーションではなく、商品でのシミュレーションによる貪欲法を考えます。まず、印字機に入る時刻で管理された商品用のヒープ(in_heap)と、印字機を出る時刻で管理された商品用のヒープ(out_heap)を持ちます。①in_heap先頭の商品と同じ入り時刻 \tauの商品全てをout_heapに入れ、現時刻を \tauに更新します。②out_heapの先頭の商品の出時刻が \tau以上であるときは印字してチャージし( \tau := \tau + 1)、そうでないときは印字しない(できない)、というのをin_heap先頭の商品の入り時刻が \tauより大きいうちは繰り返します。①と②をin_heapが空になるまで繰り返しせば、 \mathcal O (N \log N)の計算量で解けました。

コードでは、入りと出の時刻が十分大きい商品を番兵としてin_heapに入れており、これによってin_heapが空になることはないために条件分岐がシンプルになります。

コード

from heapq import heappop, heappush


def solve(N, T, D):
    in_heap = []
    for t, d in zip(T, D):
        heappush(in_heap, (t, d))
    heappush(in_heap, (10000000000000000000, 10000000000000000000))
    out_heap = []

    ans = 0
    while len(in_heap) > 1:
        tau = in_heap[0][0]
        while in_heap[0][0] <= tau:
            t, d = heappop(in_heap)
            heappush(out_heap, t + d)
        while out_heap and tau < in_heap[0][0]:
            if tau <= out_heap[0]:
                heappop(out_heap)
                ans += 1
                tau += 1
            else:
                heappop(out_heap)

    return ans


def main():
    N = int(input())
    T = []
    D = []
    for _ in range(N):
        t, d = map(int, input().split())
        T.append(t)
        D.append(d)
    ans = solve(N, T, D)
    print(ans)


if __name__ == "__main__":
    main()