注意事項
ACコードとして載せてある言語は、全てPyPy3(7.3.0)を使用しております。
有名アルゴリズムを用いる際は、他のサイトやブログへのリンクを貼らせて頂いておりますので、詳しい解説等が知りたい方はそちらを参照して頂ければと思います。
ABC257のリンク↓
A - A to Z String 2
問題概要
Aを個、Bを個、…、Zを個 をこの順に並べてできる文字列のX文字目を出力せよ。
制約
- 入力は全て整数
考えたこと
0-indexedで考えると、の各区間がA, B, ..., Zになる。よって、がそれらの区間のうちどれに入っているかを求めればよさそう。具体的には、以下の式によって求めることができる:
あとはこの を英大文字に変換したい。PyPy3ならord()で文字コードを取得、chr()で文字コードを文字に変換できるので、chr( + ord("A"))とすればよい。
ACコード
https://atcoder.jp/contests/abc257/submissions/32825247
B - 1D Pawn
問題概要
左右1列に並んだ長さのマス目がある。また、個のコマがマス目上に置かれており、コマはマスに置かれている。
これらのコマに対し、操作を回行う。回目の操作では以下を行う:
- コマを1つ右に動かす。
- ただし、そのように動かせない場合、すなわちコマが「マスにある」または「1つ右のマスに別のコマがある」場合は何も行わない。
回の操作が終わった後の各コマの位置を出力せよ。
制約
- 入力は全て整数
考えたこと
言われた通りのシミュレーションをすればよさそう。
実装上の留意点
実際に「マス目」を持つ必要はなく、各コマの位置を持った配列上で行えばよい。コマの右隣のマスに既にコマがあるかどうかは、であるかどうかで判定できる。このとき、配列外参照に気を付ける。
更に、「番兵」を設置するというテクニックがある。仮想的に、マスにコマを配置しておけば、前述の配列外参照や「マスにある」かの判定で場合分けがいらなくなる。
ACコード
https://atcoder.jp/contests/abc257/submissions/32825798
C - Robot Takahashi
問題概要
子供と大人が合わせて人おり、人の体重はである。また、0, 1からなる長さの文字列が与えられ、が0なら人は子供、1なら人は大人であることを表している。
ロボットである高橋君は、設定された実数に応じて、体重が未満の人を”子供”、X以上の人を"大人"と判定する。適切に実数を定めたときの、高橋君によって子供か大人か正しく判定される人数の最大値を求めよ。
制約
- 入力は全て整数
考えたこと
「とりあえず何かを決め打って結果を観察してみる」のが、解法を考える上での鉄則。今回はを決め打った時の高橋君の判定結果を観察してみると、を予め昇順にソートしておけば下図のような判定結果が得られることがわかる。
このとき正しく判定される人数は「左の区間内にある子供の数」+「右の区間内の大人の数」のはず。これは愚直に求めようとするととなるが、子供を0, 大人1とした数列の累積和を予め求めておけば、で求めることができる。(「子供の数」は「区間の長さ」-「大人の数」で求まる。)
あとはこれを、すなわち境目の位置について全探索すればよいので、全体で解けました。
ACコード
https://atcoder.jp/contests/abc257/submissions/32826576
D - Jumping Takahashi 2
問題概要
2次元平面上に個のジャンプ台がある。ジャンプ台はにあり、パワーはである。
高橋君のジャンプ力をとすると、高橋君は以下の条件を満たす場合に限りジャンプ台からジャンプ台に移動できる:
条件「適切に始点となるジャンプ台を1つ選べば、どのジャンプ台にも始点から0回以上の移動で到達できる」…(※)を満たす整数の最小値を求めよ。
制約
- 入力は全て整数
考えたこと
を決め打つと、各ジャンプ台の組についてジャンプ台からジャンプ台に移動できるかが条件式から定まり、頂点辺の有向グラフが構築できる。このグラフが条件(※)を満たしているかは、BFSを始点を変えながら回試すことでで判定できる。
また、は条件(※)について単調性がある。すなわち、ある値で(※)を満たした場合、それより大きい値でも必ず(※)を満たす。
「単調性がある」かつ「真偽判定が高速にできる」ときの境目を見つける問題(例えば条件を満たす最大値や最小値を求める問題)では、決め打ち二分探索が有効。すなわち、を解としてあり得る範囲で二分探索すれば、で解ける。
では解の範囲は具体的にいくらだろう。下界は明らかに0としてよい。上界は最も遠い2ヵ所にジャンプ台を配置すればよさそう。とのマンハッタン距離はで、のときは最大となるはず。よって、上界はとわかる。
以上より、で解けました。
実装上の留意点
二分探索には色々書く流儀があるが、「めぐる式二分探索」を強くお勧めする。
別解
二分探索をせずワーシャル・フロイド法を応用することでで解く解法があるらしい。
まず、各ジャンプ台を頂点とし、ジャンプ台の組についてからに重みの有向辺を張ったグラフを考える。このグラフ上で、ある(i, j)パス上の辺の重みの最大値は、そのパスを使うためのの最小値を表している。(i, j)パスは当然複数個考えられるが、それらの「パス上の辺の重みの最大値」の最小値をと定義し、これを求めたい。
(仮にこのdpテーブルが求まったとき、解はとなる。)
ワーシャルフロイド法は「中継点として使ったのがまでとしたときの暫定dpテーブル」をin-placeに求めていく。本来の遷移式は
として最短距離を求めていくが、これを
と書き換えるだけでいい(らしい)。はえーすっごい。
ACコード
本解法(決め打ち二分探索)
https://atcoder.jp/contests/abc257/submissions/32828959
別解(ワーシャル・フロイド風)
https://atcoder.jp/contests/abc257/submissions/32828081
E - Addition and Multiplication 2
問題概要
整数がある。このに対して、以下の操作を好きな回数行える。
- 整数を選ぶ。円を払い、をに置き換える。
円の予算で得られるの最大値を求めよ。
制約
- 入力は全て整数
考えたこと
できる限り大きい数字を作りたいときは
- 桁数を大きくする。
- 上位の桁に大きい数字を置く。
の優先順位で考えればよい。
予算円内で桁数を最大化したければ、最も安い数字をできる限り使うのが明らかに最適。よって、求める解の桁数はとして
だとわかる。
あとは、この桁数が達成できる範囲で9, 8, ..., 1と順に貪欲に使っていけばよい。現状の予算と残り桁数をそれぞれとして、while文でである限り整数を使う、と言う形で書くだけ。
実装上の留意点
は最大でとなるので、はintではなくlistで持つ。
ACコード
https://atcoder.jp/contests/abc257/submissions/32830865
発展的な話題
予算円を「ぴったり使い切る」必要がある場合はどうすればいいだろう?
結論を言ってしまうと、以下のdpで解ける:
数字を昇順に見ていくのは不自然に思えるかもしれないが、これがdpテーブルを埋めた後に効いてくる。
dp遷移を考えよう。
単純に考えると、数字まで使って残り予算がの状態からは、
という遷移が考えられるため、これを使ってmaxで更新していけばよさそう。
しかし、これを愚直に行うと各の組に対しての遷移が発生し、全体となるため間に合わない。
そこで、遷移を次のように捉えてみる:
数字を使うか決めるフェーズで残り予算がの状態からは、
- 追加でを1つ使う。
- を使うフェーズを終了し、を使うフェーズに移る。
とする。これなら各の組に対して遷移はとなり間に合う。
(この遷移の工夫は、個数制限なしナップザック問題とかで有名。)
さて、これで無事dpテーブルが埋め終わったので、あとはを見れば答えが…書いてない!それはそうで、まだの桁数を求めただけで具体的なの値は何も求めていない。なので、ここからはdp復元を行う。
からスタートし、「そこに書き込まれた数はどこから来たか?」を考える。先ほどの遷移を逆に辿ればよく、であれば移動、そうでないならに移動、とすればよい。
ここでdpテーブルを埋める際に数字を昇順に見た効果が出る。もしであれば、どっちに進んでも最終的にに到達できてしまうが、ここで優先的にに進むと決めることで「上位の桁に大きい数字を置く」が実現でき、最適解が自然に復元できる。
以上より、で解けました。
例題:
F - Teleporter Setting
問題概要
個の町と個のテレポーターがある。テレポーターは2つの町を双方向に結んでいて、使用することで一方の町からもう一方の町へ1分で移動できる。しかし、のときは側の繋げ先が未定であることを表している。
それぞれについて、繋げ先が未定のテレポーターを全て町に繋げたときの、町から町への最低所要時間を求めよ。(移動できない場合は-1を出力)
制約
- 入力は全て整数
考えたこと
各についてBFSすればで解けるが、当然間に合わないのでどうにか「どのについても使いまわせる情報」を見つけたい。
どのについても成り立つ性質として、「繋げ先が未定のテレポーターは全て同一の頂点に繋がれている」という点に注目する。これを、入力に合わせて「仮想頂点に繋いでいる」と捉えてあげた頂点のグラフを考えてみよう。
このグラフにおいて、繋げ先が未定のテレポーターの繋げ先をとすることは、2頂点間に重み0の辺を張ることで表現できる。
このようにして構築したグラフにおいて、間の最短経路は必ず以下の図に示す3パターンのどれかになる:
ここで、青で示した省略部分の距離は、予め頂点とからそれぞれBFSしておくことで前計算ができる。これを配列とおくと、各についての求める解は
となる。
以上より、で解けました。
ACコード
https://atcoder.jp/contests/abc257/submissions/32746559
G - Prefix Concatenation
問題概要
英子文字からなる2つの文字列が与えられる。
(相異なってもよい)の接頭辞を個連結することでと一致させられるような最小の正整数を求めよ。(不可能な場合は-1を出力)
例: = aba, = ababaab なら、 = ab + aba + ab より、
制約
考えたこと
Z-algorithmを使うことで
をで求めることができる。
これを使えば問題をDAG上の最短経路問題に言い換えることができる。すなわち、について、頂点から頂点に有向辺を張ったグラフにおいて、頂点から頂点までの最短経路を求めればよい。
DAG上の最短距離はdpで求まるのが通説。しかし、今回のグラフは辺の本数がなので、
という単純なdpでは間に合わない。
そこで、次のようなdpを考える:
初期化:
このdpテーブルを、頂点から順に見ていくことでin-placeに更新していく。
頂点まで見終わって、その時点での暫定的なdpテーブルが求まっているとする。(このdpテーブルはグラフがDAGであることと定義より単調増加であることが保証される。)
頂点を見るとき、まずdpテーブルから「以上で最左の要素」を求め(求め方は後述)、そのindexをとおく。このとき、は頂点以上に到達するまでの最小移動回数を表している。
頂点からは頂点に移動できる。ということは、距離以下である最大の頂点はに(暫定のより大きければ)更新できる。ただし、初期化をで行っているため、その場合は特別に必ず更新するようにする。
は明らかにに対して単調増加になることから、尺取法でを求めることができる。よって、でこのdpテーブルを埋めることができ、求めたかった最短距離はを満たす最小のとなる。
以上より、で解けました。
ACコード
https://atcoder.jp/contests/abc257/submissions/32850034
余談
このdpはLIS(最長増加部分列)を求めるdpから着想を得て編み出したが、どうやらこういうin-placeなdpには「実家dp」という通称があるらしい…?詳しい人がいたら教えてください (*_ _) ペコリ
終わりに
長い記事でしたが、最後までお読みいただきありがとうございます。
ABC毎にこんな解説を書いていたら死んでしまうので、今後は書くにしても勉強になりそうなものを1, 2問だけピックアップしてという感じになると思います。
まあ基本は自分の思考を整理するための自己満足の記事ではありますが、誰かの精進の糧となることを願って。ノシ