LoginSignup
1
0

PythonでABC337を解いてみたよ。(A~E問題)

Last updated at Posted at 2024-01-21

AtCoder Beginners Contest 337 (ABC337) をPythonで解きました。
見やすいコードを書く練習も兼ねてます。

A - Scoreboard

問題

$i=1,2,\cdots N$ について、$i$ 回目の試合ではチーム高橋が $X_i$ 点を、チーム青木が $Y_i$ 点を獲得しました。
得点の合計が多い方の勝ちです。どっちが勝ったでしょう?

考察

for文をつかって、チーム高橋とチーム青木のそれぞれの得点を更新していきます。

コード

N = int(input())
X, Y = 0, 0
for _ in range(N):
    x, y = map(int, input().split())
    X += x
    Y += y
if X > Y:
    print("Takahashi")
elif X < Y:
    print("Aoki")
else:
    print("Draw")

別解

sum関数をつかった解法 内包表記とsum関数をつかって下のように書くこともできます。
この問題ではそんなに恩恵を感じないですが、内包表記もsum関数もおぼえておいて損はないものなので、この機会におぼえておきましょう。
N = int(input())
XY = [tuple(map(int, input().split())) for _ in range(N)]

X, Y = (sum(x), sum(y) for x, y in XY)
if X > Y: print("Takahashi")
elif X < Y: print("Aoki")
else: print("Draw")

B - Extended ABC

問題

文字列 $S$ のすべての文字がAのとき、 $S$ を拡張A文字列といいます。
拡張B文字列、拡張C文字列についても同様に定義します。
ある拡張A文字列 $S_A$ , 拡張B文字列 $S_B$ , 拡張C文字列 $S_C$ があって、 $S_A, S_B, S_C$ がこの順に並んでいるとき、その文字列を拡張ABC文字列といいます。
入力で与えられる文字列 $S$ は拡張ABC文字列ですか?

考察

拡張ABC文字列は、昇順にソートしても形が変わりません。
これを利用するとACできます。

コード

S = list(input())

T = sorted(S)
if S == T:
    print("Yes")
else:
    print("No")

別解

collections.Counterをつかった解法

collections.Counterをつかうと、文字列 $S$ の中にあるそれぞれの文字の個数を知ることができます。

これをつかって、 $S_A, S_B, S_C$ をつくり、$S_A+S_B+S_C$ が $S$ と一致するかどうかを見ればACできます。

from collections import Counter

S = input()

C = Counter(S)
T = 'A' * C['A'] + 'B' * C['B'] + 'C' * C['C']
if S == T:
    print("Yes")
else:
    print("No")
特定の文字列が存在しないことを利用する解法 拡張ABC文字列には、"BA", "CA" "CB" の文字列は存在しません。
S = input()

for t in ["BA", "CA", "CB"]:
    if t in S:
        print("No")
        exit()
print("Yes")

C - Lining Up 2

問題

数列 $A=(A_1, A_2, \cdots , A_N)$ は、次のことを表しています。

  • $A_i=-1$ のとき、人 $i$ は列の先頭。
  • $A_i \neq -1$ のとき、人 $i$ は人 $A_i$ のすぐ後ろにいる。

数列 $A$ から、列を再現してください。

考察

列を先頭から再現していきます。
列の先頭の人は、 $A_i=-1$ となっている $i$ を見つけるだけでいいです。
問題はその先で、「列の $i$ 番目の人が分かっているとき、列の $i+1$ 番目はだれでしょうか?」の問題に高速に答えたいです。

でも、数列 $A$ からわかるのは、「列の $i+1$ 番目の人が分かっているとき、列の $i$ 番目はだれでしょうか?」の問題です。
なので、これを逆にしたのを辞書に記録します。
コードにするとこんな感じ。

"""数列Aから、逆引きの辞書をつくる"""
dic = dict()
for i in range(N):
    dic[A[i]] = i

これで、列の $i$ 番目の人が分かれば列の $i+1$ 番目の人が分かるようになり、問題を解くことができます。

コード

N = int(input())
A = list(map(lambda x: int(x) - 1, input().split()))

dic = dict()
for i in range(N):
    dic[A[i]] = i
ans_lst = [dic[-2]]
for _ in range(N - 1):
    tail = ans_lst[-1]
    ans_lst.append(dic[tail])
for i in range(N):
    ans_lst[i] += 1
print(*ans_lst)

D - Cheating Gomoku Narabe

問題

$H$ 行 $W$ 列のグリッドがあり、各マスは o, x, . のいずれかです。
以下の操作を $0$ 回以上行います。

  • . が書かれているマスを $1$ つ選び、そのマスを o に書き換える。

縦方向または横方向に連続した $K$ 個のマスであってそれらに書かれた文字がすべて o であるようなものが存在するようにすることができますか?できるならば、操作回数の最小値はいくつですか?

考察1: 次元を落とす

競プロの考察あるあるのうちの1つに、「2次元の問題は、まず1次元ならどう解くか考える」というのがあります。
この問題だと、 $H=1$ でどう解くかを考えてみます。

考察2: 1次元の問題を解く

連続した $K$ マスの中に x が入っていると、操作をしてもすべてのマスを o にはできません。
また、連続した $K$ マスの中に x がないとき、その中に含まれる . の数が操作回数になります。

つまり、 $1$ ~ $K$ 文字目の中で

  • x があるかどうか
  • . の個数はいくつか

の2つが知りたいです。

$2$ ~ $K+1$ 文字目でもどうなのか知りたいし、$3$ ~ $K+2$ 文字目でもどうなのか知りたいし。最後の $W$ 文字目までずっとこの2つが知りたいです。

このパターンはしゃくとり法がつかえます。

いろいろな書き方があると思いますが、今回はcollections.Counterをつかって、o, x, . それぞれの個数を管理することにします。コードにするとこんな感じ。

"""考察2までのコード"""
from collections import Counter

INF = 1 << 63

# (小問題に分けて解くときは、関数化しておくと実装がラクになることが多いです。)
def row_solve(row):
    w = len(row)
    if w < K:
        return INF
    result = INF
    c = Counter(row[:K])
    for i in range(w - K + 1):
        if c['x'] == 0:
            result = min(result, c['.'])
        if i < w - K:
            c[row[i]] -= 1
            c[row[i + K]] += 1
    return result

考察3: 2次元の問題に戻す

縦方向に $K$ マス選ぶことはいったんなかったことにして、横方向に $K$ マス選ぶのだけを考えればいいという条件で $H>1$ になったときのことを考えます。
1行だけの問題は解けているので、各行に対して上で書いたrow_solve関数をつかえばokです。

# 横方向だけを考えて、H>1のときにも解けるようにした関数
def solve(T):
    w = len(T[0])
    if w < K:
        return INF
    result = INF
    for row in T:
        result=min(result, row_solve(row))
    return result

あとは縦方向についても考えればゴールです。
$H$ 行 $W$ 列のグリッドを $90$ 度回転させて $W$ 行 $H$ 列のグリッドにすれば、回転前のグリッドに対して縦方向を考えるのと、回転後のグリッドに対して横方向を考えるのは一緒です。
横方向だけの問題は上で書いたsolve関数がつかえるので、$90$ 度回転させたグリッドに対してsolve関数をつかえば縦方向について考えたことになります。

下のコードでは、 $90$ 度回転させたグリッドを考える代わりに、コードを簡潔にするために $S$ を転置させた行列(反時計回りに $90$ 度回転させて左右反転させたもの)をつかっています。
もちろん(反)時計回りに $90$ 度回転させただけのものをつかってもokです。

コード

from collections import Counter

INF = 1 << 63


def row_solve(row):
    w = len(row)
    result = INF
    c = Counter(row[:K])
    for i in range(w - K + 1):
        if c['x'] == 0:
            result = min(result, c['.'])
        if i < w - K:
            c[row[i]] -= 1
            c[row[i + K]] += 1
    return result


def solve(T):
    w = len(T[0])
    if w < K:
        return INF
    result = INF
    for row in T:
        result=min(result, row_solve(row))
    return result


H, W, K = map(int, input().split())
S = [input() for _ in range(H)]
S2 = [list(a) for a in zip(*S)]  # Sの転置行列 
ans = min(solve(S), solve(S2))
if ans == INF:
    print(-1)
else:
    print(ans)

E - Bad Juice

問題

$N$ 本のジュースのうち $1$ 本だけが腐っており、飲むとお腹を壊します。
どれが腐ったジュースかを見抜きたいです。友人を 必要な最小の数 だけ呼んで、$N$ 本のジュースを好きなように飲ませるとき、その人数と、どのようにして腐ったジュースを特定するのかを教えてください。

考察1: 呼ぶ友人の数を求める

どのようにジュースを飲ませるかは後で考えるとして、いったん呼ぶ友人の数 $M$ について考えます
$M=1$ のとき、$1$ 人の友人がお腹を壊すか壊さないかの $2$ 通りあり、ジュース $2$ 本まで判別できます。
$M=2$ のとき、$2$ 人の友人のお腹を壊すかどうかのパターンは $2^2=4$ 通りあり、ジュース $4$ 本まで判別できます。
$M=3$ のとき、$3$ 人の友人のお腹を壊すかどうかのパターンは $2^3=8$ 通りあり、ジュース $8$ 本まで判別できます。

呼ぶ友人の数が $M$ のとき、ジュース $2^M$ 本までなら判別できることになります。

よって、 $N \leq 2^M$ を満たす最小の $M$ が呼ぶ友人の数です。

ここまでのコードはこんな感じ。

"""考察1までのコード"""
N = int(input())

M = len(bin(N - 1)[2:])
print(M)

考察2: 友人にどのジュースを飲ませるか決める

$M$ 個の数字があって、それぞれ $0$ か $1$ (お腹を壊すか壊さないか)で、全体として $2^M$ 通りのパターンを表せる。これは2進数と同じです。

友人 $1$ ~ $M$ をここでは便宜上、友人 $0$ ~ $M-1$ とします。
ジュースも同じく、ジュース $0$ ~ $N-1$ とします。
友人 $i$ には、ジュース $0$ ~ $N-1$ のうち、その番号を2進表記して下から $i$ 桁目が $1$ になるものを飲ませることにします。
たとえば、友人 $2$ は、ジュース$4,5,6,7,12,13,14,15,20,21,\cdots$ を飲ませることにします。
こうすれば、友人 $i$ がお腹を壊したとき、腐っているジュースの番号を2進表記すると下から $i$ 桁目は $1$ になっていることが分かります。
逆にお腹を壊していなければ、下から $i$ 桁目は $0$ だと分かります。

たとえばジュース $37$ が腐っていたとき、$37=(100101)_2$ なので、友人 $0,2,5$ がお腹を壊します。

ここまでの議論はすべて0-indexedで進めてきたので、出力の際は1-indexedに戻すのを忘れないようにしましょう。

コード

N = int(input())

M = len(bin(N - 1)[2:])
print(M)

for i in range(M):
    lst = []
    for j in range(N):
        if (j >> i) & 1:
            lst.append(j + 1)
    print(len(lst), *lst)

S = input()
ans = int(S[::-1], 2) + 1
print(ans)
1
0
0

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
0