れとろのメモ置場

とあるSEのメモ置場

AtCoder Beginner Contest 243

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

結果

A,B,C,Dの4問正解でパフォーマンスが890でした。 C問題がちょっとしたバグで何度も1WAして焦った。 E問題は後少しで解けると思うけどもう1工夫が足りなかった。

A - Shampoo

問題文の通り愚直にシミュレートすれば一応ACできる。

V<A → V-A<B → V-A-B<Cを順番に確認して比較結果がFalseになったタイミングでFかMかTを出力すれば良い。1日だと誰も不足しないかもしれないので誰かが不足するまで延々と繰り返す。

あるいは少し賢く、Vを(A+B+C)で割った余りに置き換えてから上記の比較をすれば最大3回の比較で答えられる。(A+B+C)で割った余りに置き換えることで3人が使っても不足しない日の消費量分を一括で減らせるので、はじめてシャンプーが不足するXデーのシミュレートができる。

B - Hit and Blow

Nの大きさ的に愚直にシミュレートしても間に合うと思う。その場合は2重ループを使うのでO(N ^ {2})になる。

ちょっと頑張ればO(N)で答えられる。
mapや連想配列を使ってA,BそれぞれA[value]=index,B[value]=indexとしてデータを保存しておき、 for-each文を使ってAの要素を順番に見て、value,indexを取得。B[value]が存在するかどうかとB[value]の値が取得したindexと一致するかどうかを確認することで、 AとB両方に存在するけどインデックスの位置が違う場合、AとB両方に存在してかつインデックスの位置も同じを判断できる。

C - Collision 2

愚直にシミュレートするのもややこしそうな問題だと思う。 問題文を読んで少し考えると以下のことがわかる。

  • 同じy座標の人たちだけに注目すれば良い。
    y座標が異なる人とは衝突することはないから。
  • 同じy座標にいる人達で、右に行く人の初期X座標 < 左に行く人の初期X座標 となっていればその2人は衝突する。

なので結局、y座標ごとにグループ分けをして各グループでどれか1箇所でも 右に行く人の初期X座標最小値 < 左に行く人の初期X座標最大値 を満たしていれば衝突して、どのグループでもこの条件を満たさないときは衝突しない。 細かい工夫としてグループの人数が2人以上のグループだけ条件を満たすかどうか確認していけば少し探索する量を減らせると思う。

D - Moves on Binary Tree

完全二分木の親や子へのインデックスの移り方を知っていればあんまり困らない問題だと思う。

Uが来ると親に戻るので直前の子への移動はなかったことにできる。(子に行った直後に親に戻ると、同じところに戻ったことになるのでその移動2回分がなかったことになる。)この考えでSを圧縮しようとするとスタックを使うのが楽だと思う。Sを先頭からスタックに入れつつUが来るとUはスタックに入れずスタックの1番上を取り出せばLかRとUの2文字を文字列から除去できる。 Sの最後まで確認したら順番に取り出して文字列に戻せば余計な移動を圧縮した文字列に変換できる。 (Sの長さが最大でも10 ^ {6}なので圧縮しなくてもTLEにはならない気がする。)

あとはSを順番に見ていき

  • Uが来たらXを2で割る。
  • Lが来たらXを2倍する。
  • Rが来たらXを2倍して1を足す。

を順番に処理すればいい。 Pythonなら桁数の上限がないのでこれだけでACできると思う。

C++などの言語だと扱える値には上限があるので愚直にシミュレートしているとどこかでオーバーフローする。
処理の内容が2倍とか2で割るとかだけなので2進数を連想して、2進数で考えてみると

  • Uが来たら1番下の桁を落とす。
  • Lが来たら最後尾に0を付け足す
  • Rが来たら最後尾に1を付け足す。

とすることで現在位置を管理できる。 なのでXを2進数表記に変換した文字列で管理してSの文字に応じて、文字列的に1番最後の文字を削除する、0を最後尾に付け加える、1を最後尾に付け加えるのいずれかを順番に処理していく。 最後に2進数表記を10進数表記に戻して答える。

E - Edge Deletion

難しかった。サンプルはACできるけどテストケースだと半分くらいWAになった。

Nが十分小さいのでダイクストラをN回実行しても、ワーシャルフロイド法のO(N ^ {3})で一気に全対地間分のコスト計算でもTLEにはならない。 今回はN<300とNがE問題でグラフを扱うにしては小さすぎるとメタ読みをしてとりあえずワーシャルフロイドで処理することにした。(N回ダイクストラ法を行ったほうが多少は計算時間が早い気はするけど。) 全対地間分のコスト計算が出来たとして、そこから削除できる辺を探す方法がわからなかった。

計算したA _ {i}からB _ {i}への距離とC _ {i}を比較してC _ {i}が小さいなら辺A _ {i}B _ {i}は削除できるかもしれないなとは思ったけど、その辺が頂点xの持つ唯一の辺の可能性を考慮すると、どんな条件の場合はその辺が削除できるといい切れるのかわからなくなった。

AtCoder Beginner Contest 242

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

結果

A,B,Cの3問ACでパフォーマンスが735でした。 D問題が難しかった… 解説を読んでみると解法が1クエリあたりO(t _ {i})な気がしたけどt_ {i} \leq 10 ^ {18}だからテストケースによってはTLEしそうだなあと思った。

A - T-shirt

問題文の通りに場合分けして確率を計算すれば良い。1~A位までは1.0でB+1位~は0.0。 A+1位~B位までは確率がC/(B-A)になる。
確率を求めよって言われるとちょっと苦手意識があって避けたくなる…

B - Minimize Ordering

これも問題文の通りに処理する。大体の言語にソート関数があるから、受け取った文字列Sをソートしてその結果を出力すればいい。
ソート関数の存在と使い方がわかっていれば即答できる問題。簡単すぎてちょっと困惑した、何か間違ってないのかなと。

C - 1111gal password

動的計画法で解いた。C問題は全探索すれば解ける問題が多いからC問題でDPが出てくるのはちょっと難しめな気がする。

i桁目を確定させるとi+1桁目の整数は3通り(X _ {i}-1X _ {i}X _ {i}+1)に派生するのでX _ {i}-1X _ {i}+1が0とか10になっていないかを確認しつつi桁目の時点での条件を満たすパターン数をi+1桁目に伝搬させていった。

D - ABC Transform

難しかった。手計算してみていると
- S ^ {(i)}S ^ {(i+1)}に遷移すると文字数は2倍になる。
- 0文字目の遷移には周期があるA→B→C→A→・・・
くらいは気がついた。 後は制約を確認すると1 \leq Q \leq 10 ^ {5}だったので各クエリはO(1)かO(logN)くらいで答えられないとTLEしそうだと思った。他には0 \leq t _ {i} \leq 10 ^ {18}なのでO(t _ {i})な解き方をすると最悪10 ^ {18}回の処理が必要になるのでそのクエリ単体でTLEになりそうだと思った。

とりあえず2分木の親をたどる要領でkをt回2で割って答えたい文字はもともとどの文字が遷移したものか求めた。次に、t回遷移すると1文字が2 ^ {t}文字に膨れるので、kを2 ^ {t}で割った余りを求めて最初に求めたもともとの文字がt回遷移した後の文字列の何文字目を答えればよいか求めた。最後に、最初に特定した文字をt回遷移させて出来た文字列の適切な文字数目を出力させた。
最初に考察したとおりこの解法だとO(t _ {i})なのでTLEしてダメだった。

コンテストが終わって解説を軽く読んでみたけど、解法が再帰で解くアルゴリズムで計算量がO(t _ {i})くらいありそうな気がした。 後でちゃんと読み直してみるけど。

AtCoder Beginner Contest 241(Sponsored by Panasonic)

AtCoder Beginner Contest 241(Sponsored by Panasonic)に参加しました。

結果

A,Bの2問正解でパフォーマンスが669でした。
C問題が頭で考えると簡単だけど実装が面倒な問題だった。延々とバグが取り切れなくてAC出来なかった。 D問題もざっくりとした方針は思いついたけどイテレータの操作が慣れていないからACまでは出来なかった。

A - Digit Machine

 a_{i}を遷移先のインデックスとしたときにa _ {0}から3回遷移するとインデックスがどれになるのかを答える問題。

問題の意味がわかればすんなり解ける問題だと思う。配列が使えるかどうかを問われている問題なのかなあ。

B - Pasta

 B _ {i}と同じ値のA _ {i}が何個あるのかを管理して答える問題。mapや連想配列が使えれば簡単に解ける問題だった。

同じB _ {i}が複数出てくるかもしれないのでデータ構造はmapを使うのが正解だと思う。A _ {i}、B _ {i}が小さいなら配列B _ {i}の最大値分の長さの配列でも対応はできると思うけど、今回は B _ {i} \leq 10^{9}なので配列は確保出来ないと思う。

C - Connect 6

目で見ればすぐわかるのにコードにするのは難しい… 賢い方法が思いつかなかったので愚直にチェックしていくことにして解いた。

横のチェック、縦のチェック、斜めのチェック2方向をしゃくとり法で実装しようとしたけど、少しバグってたみたい。テストケースの半分くらいはWAだった。
何も見ずに尺取法を書くのは慣れていないのもあって少し手間取った。結局ACは出来ず…
概念やアルゴリズムは理解しててもきれいにコードにするのは難しいなあ。1回きれいなコードを書いてメモしておかないとなあ。

D - Sequence Query

まずは愚直に解いてみた。
Aをvectorで用意してクエリの1つ目の値が2,3のときはソート、2分探索でx以上やx以下のインデックスを探索、k個分インデックスを移動させてそのインデックスが指す値を出力。
だけど、思っていたとおりTLE。たぶん毎回ソートするのでちょっと時間がかかりすぎていたんだろうなあ…

次に思いついたのはvectorではなくmultisetでAを処理する。setならソートの必要がないけど、同じ値が複数Aに追加される可能性を考慮して同じ値を複数含められるmultisetが適切だと思う。
あとは2分探索だった部分をupper_boundかlower_boundに置き換えて、それらで取得できるイテレータをk個分ずらせば良さそう。

問題は、k個分ずらした後のイテレータがmultisetの範囲ないかどうかを判定する部分だけど、イテレータの扱いに慣れていなくでただの不等号でbegin()、end()と比較しようとしてシンタックスエラーになった。
ここまで考えてC問題を解くのに戻ったけどCが解けなかったのでこっちに戻ってくることはなかった。

解説を少し読むと第2案がそのまま解法として載ってたから、C問題に戻らずにこっちに注力すればよかったかも。

デンソークリエイトプログラミングコンテスト2022(AtCoder Beginner Contest 239)

デンソークリエイトプログラミングコンテスト2022(AtCoder Beginner Contest 239)に参加しました。

結果

A,B,C,Dの4問正解で、パフォーマンスが719でした。 D問題がいつもに比べてかなり簡単な気がした。全探索でACできるD問題はかなり珍しい気がする。

A - Horizon

指定された計算式で計算して答えを出力すれば良い。平方根がちゃんと計算できるかどうかな問題。

出力する答えの桁数も気をつけないといけないのかも。

B - Integer Division

簡単に答えを計算できる関数があった気がするけど思い出せなかったので地道に処理していった。 基本的に10で割ったものを小数点以下を切り捨てる。あとはXがマイナスなら1引くけど、Xが10で割り切れるときはそのままにする。

C - Knight Fork

賢い方法が思いつかなかったので割と力技で答えた。

距離が\sqrt{5}程度なので各x,yの前後\sqrt{5}のうち整数の値を列挙して、格子状の点としてありえるx,y を全通り確認した。 あとは2点間の距離が2\sqrt{5}より大きい場合は条件を満たす点がいないのでNoと答えて処理を終了させる。

D - Prime Sum Game

色々考えてみたけどA,B,C,Dは100以下でとても小さいので、高橋くんが選ぶ整数と青木くんが選ぶ整数全通りを確認すればいい。
毎回整数の和が素数かどうか判定するのは手間なので最初にエラトステネスの篩等で200以下の素数を列挙しておいて、素数の判定をO(1)でできるようにしておく。

E - Subtree K-th Max

どうやって解こうかなとずっと考えていた。Q個のクエリに答えないといけないけどQが最大で10 ^ {5}なので、各クエリはO(1)で答えたい気がする。O(N)だとTLEになるし、妥協してO(log(N))くらいかなあ。
Kは最大でも20程度なので頂点iが決まったときにiを頂点とする部分木に書かれている整数のリストを事前に求めていればそのリストのデータ構造は何でも大丈夫そう。線形探索でも間に合う気がするのでsetでも使えそう。

各頂点の親と子供が管理したいからデータ構造はUnion-Findで木を作れば良さそうな気がする。

あとは、部分木のリストは葉から作ったほうが良さそうなので深さ優先探索で葉の方から見ていけば良さそう。

ここまで考えて実装して言ってたけど時間が足りなかった。コンテストが終わっても実装してみてたけど今度はランタイムエラーになったのでどこかバグってるっぽい。

解いている人数的にdiffは緑の中盤くらいだと思うからACしておきたい問題だった…

モノグサプログラミングコンテスト2022(AtCoder Beginner Contest 238)

モノグサプログラミングコンテスト2022(AtCoder Beginner Contest 238)に参加しました。

結果

A,B,Cの3問ACでパフォーマンスがXXXでした。

A - Exponential or Quadratic

nが最大で10 ^ {9}になるので2^{n}を実際に計算するのは難しいので愚直に計算して比較するのはTLEになるので無理。
高校の数学でこんな不等式を見たときは両辺対数をとってごにょごにょしてたのを連想して、両辺2の対数をとって式変形した。2 ^ {n} > n ^ {2} ⇒ log _ {2} 2 ^ {n} > log _ {2} n ^ {2}  ⇒ n > 2 log _ {2} n 最終的にはif文でn > 2 log _ {2} nを評価してYesかNoかを表示させた。

B - Pizza

最終的に何度のところに切れ込みが入っているのかを計算してから、切れ込み間が何度かを計算して最大値を求めて解いた。
回転させて切れ込みを入れるって言うのはつまり、今切った部分からA _ {i}度プラスした角度に切れ込みを入れることなので、まずは360の剰余計算をしながらA _ {i}の累積和を計算して、A _ {i}番目の切込みは0~360度のどこに切れ込みを入れているのかを計算する。 次にN個の切り込みに始点の0度と終点の360度を追加した合計N+2本の切れ目をソートする。 各中心角はx番目の切込みの角度とx+1番目の切込みの角度の差になる。 全部の中心角を計算して最大値を出力すればOK。

C - digitnum

結構難しかった。コンテストの大変の時間を考察と実装とバグ取りで消費した。なんとかAC出来たから良かったけど難しかった。

N < 10 ^ {18} なので愚直に計算するとTLEになるので無理。なので賢く計算する必要がありそう。 問題文を読んでも全く方針が思いつかなかったのでxが小さい範囲でf(x)を手計算してみた。 するとxが1桁のときはf(x)=x、2桁のときはf(x)=x-9、3桁のときはf(x)=x-99、・・・となることに気づいた。
つまりNの桁数をkとすると、f(1)からk-1桁目までのf(x)の和は比較的簡単に計算できそうだし(等比数列の和の公式を利用)、k桁目のf(x)の和は1から(N-9999・・・9)+1までの和になる。 なので後は頑張って実装した。
Nの桁数を求めて(k桁とする)、桁数に応じて1~9までの和、1~90までの和、1~900までの和、・・・をk-1桁まで求める。 次に9がk-1個続いた数字を作ってNから引く(計算結果をN'とする)。最後に1からN'+1までの総和を求めてk-1桁までの和に足す。随時998244353の剰余を計算しながら上記を計算すればAC出来た。

HHKB プログラミングコンテスト 2022(AtCoder Beginner Contest 235)

HHKB プログラミングコンテスト 2022(AtCoder Beginner Contest 235)に参加しました。

結果

A,B,C問題の3問正解でパフォーマンスがXXXでした。 D問題がずっと1WAでどんなパターンで間違っているのか全く検討がつかなかった...

A - Rotate

1桁ずつ数値を取り出せれば簡単に解ける問題。

文字列として取得して各桁の文字から'0'を引くか、数字として取得後、10の余りを取得して10で割るを繰り返せば1桁ずつ値が取得できる。

取得できればあとは数式通りに計算して結果を出力する。

B - Climbing Takahashi

問題文のとおりにシミュレートすれば解ける問題。 台の数は10 ^ {5}程度なので線形探索で十分間に合う。

C - The Kth Time Query

x _ {i}をキーにa回目は何番目の要素かをメモして各クエリに答えていけばいい。

map>でメモを用意して処理した。

D - Multiply and Rotate

ずっと1WAが取れなくてACできなかった。

作れる値をノードとしたグラフで1からNへの最短経路の距離を求める問題とみなせる。 グラフの最短経路なので幅優先探索で解けば良さそうなのはわかった。あとは1から計算すると数値がどんどん大きくなるので大きくなりすぎると探索を打ち切る必要がある。

もしくはNから始めて1を目指す。

方針は間違ってないと思うけどずっと1WA。一体何を見落としていたのかわからない...

(追記) コンテスト中はNから1を目指す方法で解いてたけど、1からNを目指す方法でとき直すと1回でACできた... コンテスト中も始めは1からNを目指す方法で解いてて、サンプルケースでTLEしそうなケースが出てきたからNから1を目指す方法に方針転換したけど、それが裏目に出たみたい...
悲しい

AtCoder Beginner Contest 234

AtCoder Beginner Contest234に参加しました。

結果

A,B,C,D問題の4問正解でパフォーマンスが840でした。 D問題が割と解けるようになってきたので次はE問題が安定して解けるようになりたい。

A - Weird Function

問題文を読むとめんどくさそうな数式があったからどうしようと思ったけど、f(x)を関数として定義すれば簡単に解けるので数式を全部関数化して解いた。
自作関数を作成して使えるかどうかの問題だと思った。

B - Longest Segment

聞かれている内容自体は距離の最大値はどれくらいかという簡単な問題。制約を確認すると N\leq 100なので二重ループを使った全通りでも間に合うと判断した。
なので二重ループで全組み合わせを試しつつ、2点間の距離を計算して最大値を更新していけば解ける。

C - Happy New Year!

1番小さいものからいくつかを考えてみるとKを2進数表記しつつ1を2に置き換えた値がそのまま答えになると気づいた。 なので、受け取ったKを2進数に変換して1を2に置き換えた文字列を出力する。
始めはKを受け取りつつ、ビット全探索でiビット目が1かどうかを判断する処理を流用して処理しようとしたけどサンプルの時点で何故か一部の答えが合わなかったので諦めて2進数への変換処理に置き換えて解いた。

D - Prefix K-th Max

始めはDP使って解くのかなと思いつつ、手計算していくとDPじゃなさそうとなった。
ちょっと考えてみると、K番目に大きい値は、新しく注目するP _ {x}の値によって現状維持か、K-1番目に大きかった値と入れ替わるかのどちらかになることに気づいた。今答えた値よりP _ {x}が小さい場合は現状維持。 今答えた値よりP _ {x}が大きい場合は答える値が更新される。なので1~K-1番目までをソートして管理しつつP _ {x}が大きかったときはP _ {x}を含めたK個の中から最小のものを取得できれば良さそう。

ここまで考えて、1~K-1の値を優先度付きキューで管理すればソートも最小の値の取得も簡単にできるので、優先度付きキューを使って解いていった。