こんにちは、こんぶちゃ(茶コーダー)です!
初心者視点から競プロAtCoderのABC355のA問題〜C問題の自身の解答と解答説明をしていきます。
まだまだ学習の身ですので、最適コードではない部分も多々あるかと思いますが、その部分は同じ学習者として温かい気持ちで応援&ご助言頂けば嬉しいです。
では早速行ってみましょう!
今回の参加コンテスト(ABC355)
A問題:Who Ate the Cake?
問題文
高橋君のケーキが誰かに食べられてしまいました。ケーキを食べた犯人の候補として、人 1、人 2、人 3 の三人が挙げられています。
犯人の目撃者はりんごさんとすぬけくんの二人がいます。りんごさんは人 A が犯人でないことを覚えており、すぬけくんは人 B が犯人でないことを覚えています。
二人の記憶から犯人を一人に特定できるかどうか判定し、特定できるならばその人の番号を出力してください。
解答
# りんごさんとすぬけくんの証言を読み込む(犯人でない人の番号をセットに格納)
ab = set(map(int, input().split()))
# 犯人の候補となる人のリストを作成
abc = [1, 2, 3]
# 証言から犯人でない人をリストから除外
for p in ab:
abc.remove(p)
# 残ったリストの要素が一人であれば、その人が犯人
if len(abc) == 1:
print(*abc) # 犯人の番号を出力
else:
print(-1) # 犯人を特定できない場合は-1を出力
考え方)
犯人の候補となるリストabc = [1, 2, 3]を作成し、証言から犯人でない人をリストから除外していきます。残った人数が一人の場合は、犯人を特定でき、そうでない場合は犯人が特定できない(-1を出力)となります。
コード詳細
- input() 関数でりんごさんとすぬけくんの記憶から犯人でない人の番号を取得します。この番号はスペース区切りで入力されます。
- map(int, input().split()) で入力を整数に変換し、set() に変換して ab に格納します。
- 全ての候補者(1, 2, 3)の番号を abc リストに格納します。
- for ループを使って ab 内の番号を abc から削除します。
- 最後に、abc の長さを確認し、長さが1であればその番号を出力し、そうでなければ -1 を出力します。
B問題:AtCoder Amusement Park
問題(要約)
長さ N の数列 A=(A1,A2,…,AN) と、長さ M の数列 B=(B1,B2,…,BM) が与えられます。ここで、A,B のすべての要素は互いに相異なります。A,B のすべての要素を昇順に並べた長さ N+M の数列 C=(C1,C2,…,CN+M) において、A に現れる要素が2つ連続するかどうか判定してください。
解答
# 入力を受け取り、数列の長さ n, m を取得
n, m = map(int, input().split())
# 数列 A を取得
A = list(map(int, input().split()))
# 数列 B を取得
B = list(map(int, input().split()))
# 数列 A と B を結合し、昇順にソートした数列 C を作成
C = sorted(A + B)
# 数列 C の要素を順にチェックし、A の要素が連続しているかを判定
for i in range(n + m - 1):
if C[i] in A and C[i + 1] in A:
# 連続している場合は "Yes" を出力して終了
exit(print("Yes"))
# 連続していない場合は "No" を出力
print("No")
考え方)
AとBの組み合わせ配列Cを作成し、昇順にソートします。ソートした配列Cの i 番目と i+1 番目を確認し、配列Aの要素が連続しているかを判定します。
コード詳細
- input() で数列AとBの長さを取得し、map(int, input().split()) でそれを整数に変換して n と m に格納します。
- map(int, input().split()) を使って数列Aを取得し、リストに変換して A に格納します。
- 同様にして、数列Bを取得し、リストに変換して B に格納します。
- 数列AとBを結合し、sorted() 関数で昇順にソートして新しい数列 C を作成します。
- for ループを使って C の各要素をチェックし、数列Aの要素が連続しているかを判定します。
- 要素が連続している場合は “Yes” を出力し、プログラムを終了します。
- 連続していない場合は、ループを終了した後に “No” を出力します。
C問題:Bingo 2
問題
縦N行、横N列のマス目があり、各マスには特定の整数が書かれています。Tターンにわたって整数が宣言され、宣言された整数が書かれたマスに印をつけます。ビンゴ(横列、縦列、または対角線のいずれかのN個のマスにすべて印がつくこと)を初めて達成するのは何ターン目かを求めます。ビンゴを達成しない場合は-1を出力します。
解答
# 入力を受け取り、マス目のサイズ n とターン数 t を取得
n, t = map(int, input().split())
# 各ターンで宣言される整数のリスト A を取得
A = list(map(int, input().split()))
# 行と列の印の数を記録するリストを初期化
rows = [0] * n
cols = [0] * n
# 対角線の印の数を記録する変数を初期化
diag1, diag2 = 0, 0
# 各ターンの整数を処理
for turn, a in enumerate(A):
# 整数 a の位置を計算
i = (a - 1) // n
j = (a - 1) % n
# 行と列の印の数を更新
rows[i] += 1
cols[j] += 1
# 対角線の印の数を更新
if i == j:
diag1 += 1
if i + j == n - 1:
diag2 += 1
# ビンゴを達成したかチェック
if (rows[i] == n) or (cols[j] == n) or (diag1 == n) or (diag2 == n):
exit(print(turn + 1))
# ビンゴを達成しない場合は -1 を出力
print(-1)
ポイント)
ビンゴが揃う条件は下記の条件になります:
- 横 n 通り
- 縦 n 通り
- 斜め 2 通り
宣言される数字は重複がないため、各条件において n回宣言されるとビンゴになります。また、それぞれの条件は下記のように求めることができます:
- 横: 宣言された数字*1を n で割った時の商 ( iと定義)
- 縦: 宣言された数字*1を n で割った時の余り ( jと定義)
- 左上から右下の斜め: i と j が同じ場合
- 右上から左下の斜め: i + j が n – 1 と同じ場合
*1 正確には、宣言された数字 – 1 を用いています。
上記の条件が満たす場合にビンゴになりますので、この時のターンを求めることにより解に辿り着けます。
解き方(詳細):
- input() でマス目のサイズ n とターン数 t を取得し、map(int, input().split()) でそれを整数に変換して格納します。
- map(int, input().split()) を使って各ターンで宣言される整数のリスト A を取得します。
- 行と列の印の数を記録するために、rows と cols リストを初期化します。
- 対角線の印の数を記録するために、diag1 と diag2 を初期化します。
- 各ターンの整数 a を処理し、その位置 i と j を計算します。
- rows と cols リストで行と列の印の数を更新します。
- 対角線の印の数を更新します。i と j が同じ場合は diag1 を、i + j が n – 1 の場合は diag2 を増加させます。
- ビンゴを達成したかをチェックし、達成した場合はターン数(1から始まるので turn + 1)を出力してプログラムを終了します。
- ビンゴを達成しない場合は、全てのターンを処理した後に -1 を出力します。
感想
今回はC問題まで解けました。
A問題は、二人の証言から犯人を探す問題でした。if文を駆使して解く方法もありますが、容疑者リストを作り、証言からリストを削除していく方式で解くことができました。
B問題では、二つの数列の組み合わせ・並び替え・元の配列確認を行う問題でした。組み合わせと並び替えはスムーズに行うことができましたが、元の配列のどちらに当たるかを確認する方法に初めは戸惑いました。並び替えた配列を順に2つずつ確認することで解決することができました。
C問題は、ビンゴが何ターン目で成立したかを回答する問題でした。ビンゴの成立条件を一般化することができずに苦戦しましたが、各通りがnに達することで条件が成立することがわかったので、解くことができました。
今回はなんとかC問題まで解けました。解くまでに時間がかかってしまったこともあり、レーティングは変わらずの558でした。
皆さんも競技プログラミングを楽しんでくださいね!(*^▽^)/
コメント