れとろのメモ置場

とあるSEのメモ置場

AtCoder Beginner Contest 352

AtCoder Beginner Contest 352に参加しました。

結果

A,B,C問題の3問正解でした。

コンテスト後にD問題の解説を見たら結構惜しいところまでは解けていたっぽい。採用するデータ構造を誤っていて処理に余計な時間をかけていたみたい。数列の最大値、最小値を取得するのにsetが使えるのは自分の中には無い発想だったのでためになる失敗だったと思う。

A - AtCoder Line

駅1から駅NまでのN個の駅があり、X駅からY駅へ移動する。このときにZ駅を通るかどうか答える問題。

XとYの間にZがいるかどうかを判断してYes/Noを出力するだけの問題。

X,Yの大小関係が不明なので大小比較してスワップして常にXがYより小さい/大きいを固定してから X,Yの間にZがいるかどうかを確認すれば良い。 大小関係的には X \leq Z \leq Y (X \leq Yの場合)になっているかどうかをif文で判断すれば早い。 もしくはループ処理で1つずつ確認していっても制約的には間に合う。

B - Typing

タイプミスしながらある文字列(T)を入力したので、もともと入力したかった文字列(S)の各文字が正しく入力できている文字列Tの位置を順番に答える問題。

文字列Sの文字を文字列Tの前から順番に探して答えていけば解ける問題。 文字列はそこそこ長い可能性があるので、文字S _ {i}を探すときは文字列Tの先頭からじゃなくて、S _ {i-1}を探すときに途中まで探索していた文字列Tの続きから探索を始めていくと言う工夫は必要そう。

C - Standing On The Shoulders

巨人がN人いて、それぞれの肩の高さA _ {i}と頭の高さB _ {i}が与えられる。※地面を基準としたときの高さ。

巨人がそれぞれの肩に立って積み上がっていく時、巨人の頭の高さとして実現できる最大値を求める問題。

N人目の巨人の足元の高さはN-1人の巨人の肩の高さの和になる。 だからN人目の巨人の頭の高さは、N-1人の巨人の肩の高さにN人目の頭の高さを加えた位置になる。

N人の巨人それぞれが1番上にいる場合の頭の高さを計算して、最大値を出力すれば解ける。

N-1人の巨人の肩の高さを毎回計算しているとTLEしそうなので工夫が必要そう。 予めN人分の巨人の肩の高さの合計を計算しておき、巨人iが1番上だった場合は、肩の高さの和からA _ {i}を引いてB _ {i}を加えることで巨人iの頭の高さをO(1)で算出できる。

D - Permutation Subsequence

1~Nを並び替えて作った数列が与えられるので、i~i+Kの各位置の最大値と最小値の差の最小値を出力する問題。

色々考えてみた結果としては以下の通り。

  • サイズKのウインドウをスライドさせているわけなので、1~N-Kまでが探索の範囲になる。N-K+1からK個数値を探していくと最後の数値はN+1になって数列の最大値を超過する。探すだけ無駄なのでどこからK個見るかの基準には1~N-Kで間に合う。
  • 与えられる数列は1~Nを並び替えたものなので、それを元に値iは数列のどの位置にいるのかを管理している方が処理が楽そう。数列の端から端まで探索してKの値を探すより、1はどこ、2はどこというのを管理しておけばK個の値を参照するだけでその時の処理対象の位置が取得できる。

という事で各要素にランダムアクセスできて、先入れ後出しができるデータ構造が適していそうと思ったのでdequeを使って解くことにしてみた。 1つ要素を追加するときに最大値を更新して、1つ要素を削除するときに最小値を更新しながら、常に要素数がK個になるようにしながら最大値と最小値の差を計算して最後に差の最小値を出力してみた。

けどTLEでダメだった。

結局コンテスト中には解ききれなかったのでコンテスト後に解説を読んでみたら、 データ構造としてはsetを使うのが正解だったみたい。setだと最大値と最小値の取得が簡単にできるので要素を更新するたびに最大値・最小値をメンテする必要がなくなって、その分処理が早くできるっぽい。

確かにdequeを使って管理していると、すでにキューに入っているどれかが新しい最大値・最小値となる場合は毎回K個全てを探索しないといけなかったのでTLEの原因だろうなとは思っていた。

AtCoder Beginner Contest 351

AtCoder Beginner Contest 351に参加しました。

結果

A,B,C問題の3問正解でした。

D問題解いてみたらTLEになってて、結局時間内には正解できなかった。でも解説を見てみると解き方の方針自体はTLEになった回答と同じだった... 何か無駄な処理でもあってTLEになったのかなあ... ちょっと納得いかない...

A - The bottom of the ninth

野球が9回表まで終わっているので9回裏のチームは何点以上取れば勝てるかを答える問題。

各回での2チーム分の得点が与えられるのでチーム別に集計して、後攻側のチームが何点取れば先攻側の得点+1点になるかを計算して出力する。
問題文の通り処理するだけのいつものA問題

B - Spot the Difference

1箇所だけ異なる文字列が1組与えられるので、文字列が異なる位置を答える問題。

文字列から任意の1文字だけを参照する方法を知っていれば解ける問題。 ループで全通りのチェックと文字列比較と文字列から任意の1文字の取得ができれば解ける問題。

C - Merge the balls

数列が与えられるので、空の列に指定された手順で処理しながら1つずつ移動させていき、最終的に列の要素数を答える問題。

空だった列に移した後、最後尾2つを対象に処理を行っていく。
数値2つの値が同じなら2つを取り除き+1した値を数列へ加えて、2つが異なる値だったら次の値を列へ移動させる。

最後尾2つの値を処理したいので空の列としてスタックを使うことにした。 最後尾2つは2回ポップすれば取り出せるので、することがなくて元に戻す時の順番だけ気をつければ今回の問題にあったデータ構造だと思う。

もともとの問題文だと数列の値は2 ^ {A _ {i}}で列の最後尾2つの値が同じ時はその数値の和を列に追加するという設定だったけど、0 \leq A _ {i} \leq 10 ^ {9}だったのでバカ正直に累乗を計算して値を列に加えていくのは現実的じゃない。そもそも2 ^ {10 ^ {9}}とか大きさ的に扱える範囲を大きく越えてそう。
指数部分だけ管理すればなんとか扱える範囲で収まるし、同じ数の和を計算すると指数が1増えるだけでするので、わざわざ指数を元に数値を計算して、和を計算して、指数表記だと2の何条になるか求めてってことはしなくて済む。

一通り数列の値を列へ移し終えると、要素数はスタックのサイズを出力するだけで済むので最終的な要素数を1つ1つ数えないで済む。

スタック以外でも配列なら頑張れば対応できると思う。キューで処理するにはちょっと無理がある。

どのデータ構造を使うのかと、そのデータ構造で何を管理するのかを正しく設定できれば後は指定されたとおりに処理するだけの問題になるのでそこまで難しくなくなると思う。

D - Grid and Magnet

グリッドが与えられて、一部のマスには磁石がある。磁石と隣り合うマスに移動するとそれ以上は移動できなくなる。こういった設定のときに、そのマスから移動可能なマスの数をそのマスの自由度と定義する。 この時自由度の最大値を求める問題。

磁石と隣り合わないマス同士が隣り合っていれば、そのマス達の自由度は同じになる。 なので、磁石と隣り合わないマス同士をグループとして1まとめにし、グループごとに自由度を求めて、自由度の最大値を出力すれば解けると考えた。

と言う事で大雑把な方針としては

  1. Union-Find木で磁石に隣接しているマス以外でグループを作る。
  2. グループ毎に自由度を求める。
  3. 求めた自由度の内最大値を出力する。

として解いてみた。 けど、TLEでダメだった。

AtCoder Beginner Contest 350(Promotion of AtCoderJobs)

AtCoder Beginner Contest 350(Promotion of AtCoderJobs)に参加しました。

結果

A,B,C,Dの4問正解でした。

個人的にはC問題が面白いなと思った。自分でソートを実装してどことどこを入れ替えるかを答えるというのは、 ソートの種類ごとの計算量とかを把握していないと実装はできてもTLEしたり交換はN-1回以内とかの制約に引っかかると思う。バブルソートなんて実装したらとてもじゃないけど通せない。

A - Past ABCs

ABCXXXのようにABC+数字3桁の6文字が与えられるので、それはこれまでに開催されたコンテストの略称かどうかを答える問題。 開催されたコンテストはABC001からABC349の範囲でABC316だけは誤答になるのでそこだけは例外的に処理が必要。

単純に処理するなら文字列の大小比較するだけで解けると思う。 ABC001以上かつABC349以下であり、ABC316ではない時はYesと出力して違うときはNoと出力するだけの問題。

プログミング言語的に文字列の大小比較がただの演算子でできるなら特に困らない問題だと思う。

最初の3文字はABCで固定なので後ろの三文字を取り出してどうにかして数値に変換して・・・と処理しても良いけど面倒くさい。

B - Dentist Aoki

最初はN本の歯があってQ回治療を行う。T_{i}番目の歯があるなら歯を抜いて、歯がないなら歯を生やす(生やす!?)。 最終的に生えている歯の本数を答える問題。

T_{i}が出てきた回数が奇数か偶数かで、その歯は最後抜かれたのか生やされたのかが判断できる。 配列でどの歯を何回治療したのか管理して、最後に治療回数が偶数回の歯の本数を数えて出力すればよい。

C - Sort

数列が与えられるのでN-1回以内のスワップでソートする問題。 どの位置の数字とどの位置の数字を入れ替えるかを順番に出力して答える。

ソートのやり方はいろいろなアルゴリズムがあるのでまずはその中からどれを実装するか決めて、スワップするときにどことどこを入れ替えたのかを記録していけば解けると思う。
ソートアルゴリズムの計算量を把握して置かないとO(N^{2})とかのアルゴリズムで実装するとTLEになるので注意が必要。

はじめはクイックソートを実装しようとしたけどテストケースのいくつかがWAになった。サンプルは問題ないから微妙なパターンでバグがあったのかなあ。 クイックソートがダメだったので選択ソートをベースにしつつ連想配列でどの値がどの位置にあるのかを管理して、未ソートの中の最小値がどこにいるのかをすぐに分かるようにして実装した。

D - New Friends

N人の人がいて、友達の友達は友達と言う考えで友達を増やしていった場合、ユーザー全体では今の状態から最大何人友達を増やせるかを答える問題。

サンプルを手計算しながら考えてみると、以下のことに気づいた。

  • 人のつながりの状況によってはいくつかグループができる。
  • グループ内だと全員と友達になれる。(友達の友達は友達になれて、その新しい友だちの友達とも友達になれるので、結局グループ内の全員と友達となれる。トポロジー的にはフルメッシュになる。)

なので解く方法としては、人と人とのつながりを元にグループ分けする。 グループ内では未設定の友達関係が何組あるのかを数える。

グループ分けする方法は深さ優先探索幅優先探索、Union-Find木を使うなどいろいろやり方がある。

グループ内で新規に友達となれる組がどれくらいあるのかはグループ内に誰がいるのかと現状何組の友達がいるのかを元に数えていく必要がある。けど、グループごとに配列を用意してまとめて、二重ループでこの組は今友達なのかどうかを確認して、数えてとやってみたらTLEとかWAとかMLEとかになった...
よくよく考えると知りたいのは新規に友達となれる組数だけで、誰と誰が友達になれるのかはあまり重要じゃない。 グループごとの人数と、グループ内の友達の組数だけわかれば、最大何組の友達関係が構築できる中で、今この組数しか構築できていない。よって新規に何組が友達になれるかが計算できる。 なのでこの組み合わせは友達かどうか確認するのをやめて計算して友だちになれる組数を全グループ分求めて答えることにした。

AtCoder Beginner Contest 349

AtCoder Beginner Contest 349に参加しました。

結果

A,B,Cの3問正解でした。 D問題が解けそうな気がして頑張ってみたけど解ききれなかった。

A - Zero Sum Game

1対1で勝負して勝つとポイントが+1、負けるとポイントが-1するゲームでN-1人のポイントが教えられるのでN人目のポイントを答える問題。

何試合してもN人全体のポイントの合計は0のままなので、そこに気づけていたら簡単に解ける問題。 N-1人のポイントを合計して、正負を反転させた値を出力すれば解ける。

B - Commencement

文字列が与えられるのでどの文字が何回出てくるか数えて、i回出てくる文字が0種類か2種類だけになっているかを判定する問題。

i回出てくる文字がそれぞれ何種類あるか知りたいので、 まずはどの文字が何回出てくるかを数える。 次にi回出てきた文字が何種類あるかを数えていき回答を出力する。

連想配列を2つ使えば簡単に解ける問題。 1つ目の配列で文字をキーに出現回数をカウントし、 2つ目の配列で出現回数をキーにその回数出てきた文字の種類数をカウントする。

最後に2つ目の配列の各キーに紐づく値が0か2だけになっているかどうかを確認して回答を出力する。

C - Airport Code

長さ3以上の文字列S(英小文字)と長さ3の文字列T(英大文字)が与えられるので、 大文字小文字を無視して、TがSの空港コードとなっているかどうかを判定する問題。

前処理として文字列SとTの大文字小文字をどちらかに統一してから処理する。

あとはSを先頭から見つつ、Tの文字列が順番に出てくるかを確認して回答を出力する。

トヨタ自動車プログラミングコンテスト2024#4(AtCoder Beginner Contest 348)

トヨタ自動車プログラミングコンテスト2024#4(AtCoder Beginner Contest 348)に参加しました。

結果

A,B,Cの3問正解でした。

D問題は、これで解けるだろうと思ったアルゴリズムを実装してもTLEが出て最後まで解決できなかった。 解説を斜め読みした感じだと、思っていたアルゴリズムだとダメだったみたい。

A - Penalty Kick

問題文のとおりに処理して答えるだけの問題。

1~Nまでの整数のうち3の倍数ではない数の個数を答える問題。 剰余演算で1個ずつ3の倍数かどうかを確認して数えれば良い。

ループ処理と剰余演算ができれば解ける問題。

B - Farthest Point

N個の点の座標が与えられるので全組み合わせで2点間の距離を計算して、 各点から1番遠い点の番号を順番に答える問題。

指定されている距離の定義はユークリッド距離だけど、ルートが出てくるので距離は小数点以下の値が出てくることになる。 小数点以下に値があるということは誤差が出てくるかもしれないので、できるだけ整数の範囲で解きたい。 問題をよく読んでみると、答えには求めた距離を出力する必要はなく値の大小関係だけが必要なので、わざわざルートを取る必要はない。

なので2重ループで全組み合わせを試しつつ、ユークリッド距離の2乗を元に各点から1番遠い点の番号を求めて、 順番に出力していった。

C - Colorful Beans

色と美味しさがセットになった情報がN個あるので、色でグルーピングしつつ、各グループの美味しさの最小値を求めてから、最小値の中の最大値を出力する問題。

色ごとに美味しさの最小値を管理したいので連想配列を使って色ごとにグループを作る。 あとはグループごとの最小値を集めて、その中の最大値を求めて出力すれば良い。

連想配列が扱えるかどうかな問題だと思う。

D - Medicines on Grid

グリッドの一部に障害物があってエネルギーを1消費して隣接するマスへ移動できる。一部のマスには薬があって、使えばエネルギーを指定の値に変更できる。
この時指定されたスタートからゴールへ移動できるかどうかを答える問題。

ただの指定されたスタートからゴールへ移動可能かどうかを答える問題なら深さ優先探索とかで解けば良い、よくある問題。それに対して今回はエネルギーという設定があるのがちょっと違う部分。

だと思って各マスに到達できる最大のエネルギー残量を更新しながら幅優先探索で解いてみたらTLEになった。そのあといろいろ試行錯誤してたけど全然解消できなかった。

AtCoder Beginner Contest 347

AtCoder Beginner Contest 347に参加しました。

結果

A、Bの2問正解でした。

C問題がわからなかった。考えれば考えるほど、この解き方だとこのパターンをうまく落とせないとか、解けはするけどTLEになるなあとか出てきてダメだった。

C問題だけど一旦飛ばしてD問題とかを解きに行ってたほうが良かったのかも。 (C問題が解けないなんて思ってもいなかったけど。)

A - Divisible

指示通りに処理する問題。

大体の言語だと%を使えば倍数かどうかの判定ができるので、知っていれば特に困らない問題。 標準の入出力と数値計算ができれば解ける問題。

B - Substring

文字列が与えられるので部分文字列の種類数を答える問題。

種類数を答える問題なので重複を除去して答えの候補となる部分文字列を管理したい。なのでsetを使いたい。
setさえ使えれば、部分文字列をsetに入れていき最後にsetのサイズを出力するだけで済む。

C - Ideal Holidays

1週間が休日A日間、平日B日間ある世界で、N個の予定がそれぞれ今からD _ {i}日後にある。 今日が1週間のいつなのかわからないけど、予定全てが休日の可能性があるかどうかを答える問題。

考えれば考えるほど難しいなあと思った問題。

とりあえず何日後と言う情報はそれほど重要じゃなく、何曜日かが重要なので、 D _ {i}をA+Bで割ったあまりに置き換えて、何曜日に予定があるかどうかで考えることにした。 ついでに、その曜日に1度でも予定が入っていればそこの曜日は休日になって欲しい曜日候補になるので、 setにD _ {i}をA+Bで割ったあまりの情報を入れて重複を除去することで扱うデータ量を減らすことにした。 ここからが全然うまくいかなかった。

愚直に解くなら x番目の曜日を休日の始まりとしてそこからA日間で全ての予定が収まるかどうか を0 \leq x \leq A+Bで確認して1度でも予定が全部休日に収まるxがあれば計算終了で、解答できる。
でもこの方法だと1 \leq A,B \leq 10 ^ {9}の制約的にTLEするパターンがあり得る。

他の方法もいくつか考えてみたけどTLEするパターンしか思いつかなかった。

予定のある曜日と曜日の間が何日空いているかを元になんとかならないかなとも思ったけどうまくいきそうな方法が思いつかなかった。

ユニークビジョンプログラミングコンテスト2024 春(AtCoder Beginner Contest 346)

ユニークビジョンプログラミングコンテスト2024 春(AtCoder Beginner Contest 346)に参加しました。

結果

A,B,Cの3問正解でした。

D問題以降が全然解けなかった。
D問題は問題を読んだ感じだとDPっぽいと思いつつ条件を満たすのはどうすればいいんだろうなあと悩んで解けなかった。 E問題は全然わからなかった。2次元のセグメント木でも実装できれば簡単に処理できそうだなあと思った。

A - Adjacent Product

問題文の通り処理する問題。

N個数値を受け取るのでi番目とi+1番目の積を順番に出力する。
標準入力と数値計算と標準出力ができれば解ける問題。

B - Piano

鍵盤が無限に長いピアノの白鍵と黒鍵をwとbで表した時、白鍵と黒鍵の数(W,B)が指定された数になる区間があるかどうかを答える問題。

鍵盤1周期が12文字で表されるので数える時の開始位置を12通り試しながら、 開始位置からW+B個分の鍵盤の色を数えていき指定した数と一致するかどうかで確認できる。

文字列に0-インデックスでランダムアクセスできるなら、 文字列[index]のindexを鍵盤の長さで割った時のあまりに置き換えることでindexを+1し続けても、 参照する位置が文字列の最後から先頭へループしてくれるので 色を確認してカウントする作業に専念できる。

C - Σ

正整数Kと数列が与えられるので、 K以下の整数のうち数列に含まれない整数の総和を求める問題。

K以下の総和は(K+1) \times K /2で計算できる。 そこから、数列に含まれるK以下の数を各種1回ずつ引いていけば答えが求められる。

数列には同じ値が複数存在する可能性があるので数列の中身をsetに入れて重複する値を除去すれば、 各数値を1回だけ処理することができる。