Processing math: 100%

AtCoder Beginner Contest 371 F,G問題メモ

F - Takahashi in Narrow Road

問題文

  • 東西に無限に続く道があり、道の上には N 人の人がいます。
  • i 番目 (1iN) の人は、はじめ原点から東に Xi メートル進んだところにいます。
  • 人は道の上を東西に動くことができます。
  • 具体的には、次の移動を好きなだけ行うことができます。
    • 人を一人選ぶ。移動する先に他の人がいない場合、選んだ人を 1 メートル東に、もしくは西に移動させる。
  • 合計 Q 個の用事があり、i 個目 (1iQ) の用事は次の形式で表されます。
    • Ti 番目の人が座標 Gi に到着する。
  • Q 個の用事を先頭から順にすべて完了するために必要な移動回数の最小値を求めてください。

制約

  • 1N2×105
  • 0X1<X2<<XN108
  • 1Q2×105
  • 1TiN (1iQ)
  • 0Gi108 (1iQ)
  • 入力はすべて整数

解法

Ti が目標地点 Gi に行くまでに他の人がいたら、ズリズリと押していく感じになる。
その押した分の人の移動コストも計上される。

たとえば用事 i によって人 TiGi に行くのに右(東)に移動するとして、ひとまず以下の解法が考えられる。

  • k 人分 右の人 Ti+k1 まで影響する(移動が必要になる)とする
  • TiTi+k1 の現在の座標の合計値 s を得る
  • 各人の移動先の座標は公差1の等差数列なので、和 tk(Gi+Gi+k1)2 で求められる
  • ts がその用事のコストの和

なので、「各用事によってどの人まで影響するか」「その間の人の現在の座標の合計値」がわかればよい。

影響する人は必ず連続した区間になるので、座標をセグメント木などで管理すれば合計値はわかりそうだが、 そのままだとどの人まで影響するのかわかりづらい。また、移動後の座標も等差数列で更新する必要があり、 できなくはなかったはずだが、ややこしい。

典型の一種で、1ずつのズレを最初から織り込めば実装が楽になる。

  • Yi=Xii として定義する
  • 用事を、YTiHi=GiTi にするものと捉える

こうすると、影響する全ての人の目標地点が Hi となる。

どこまで影響するかも、「Ti より右で、現在の YjHi より小さい人まで」(右に移動する場合)、 「Ti より左で、現在の YjHi より大きい人まで」(左に移動する場合)となり、二分探索が使える。

また、移動後の更新も、区間を同じ値で更新すれば良いので、等差数列よりは実装しやすい。

ただ、区間更新・区間和取得が必要になるので遅延セグメント木を使うのだが、その木の上で二分探索を行う必要がある。
AtCoderLibraryの遅延セグメント木 にはその機能がある(max_right, min_left)が、自作ではそこまで実装してない人もいそう(というか自分がしてなかった)。
いい機会なので、AtCoderLibraryの実装を参考に機能追加しておいた。

ノードに (区間長, 区間内の Yi の最小値, 最大値, 合計値) を持たせれば、 左右いずれにもの影響範囲の二分探索、および区間合計値の取得がおこなえる。

Python3

G - Lexicographically Smallest Permutation

問題文

  • (1,2,,N) の並べ替え P=(P1,P2,,PN),A=(A1,A2,,AN) が与えられます。
  • あなたは、次の操作を 0 回以上好きな回数行うことができます。
    • i=1,2,,N に対して一斉に AiAPi で置き換える。
  • 得られる A としてありえるもののうち、辞書順で最小のものを出力してください。

制約

  • 1N2×105
  • 1PiN (1iN)
  • PiPj (1i<jN)
  • 1AiN (1iN)
  • AiAj (1i<jN)
  • 入力はすべて整数

解法

indexに混乱しやすいので、落ち着いて問題を把握する。

初期状態と途中状態が分かりづらいので、Ai と言ったときには初期状態の A を指すことにする。

置換グラフ は、いくつかのサイクルに分解できる。

iPi を辿っていって、たとえば 137101... のサイクルが繰り返されるとしたら、 i=1 の位置に来る値は A1A3A7A10A1... と変わっていくことになり、それ以外の値には変化しない。

辞書順最小なので、i=1 には、持ってこられる中で一番小さい値を持ってきたい。
幸い、A に同じ値は無いので、持ってくる Ai は一意に決まる。
上例では、(A1,A3,A7,A10) の中から選べばよい。

この時、答えを作るための最適な操作回数を X として、Xrmodmr,m が決まったことになる。
上例で A7 が最小だったとして、2回、6回、10回、、、の操作を行ったときに i=1 の位置に A7 が来るので、 m はサイクル長の 4r はその中の最適な操作回数 2 として X2mod4 ということが決まる。

これで、1を含むサイクル(i=1,3,7,10)の値が決定する。

次、最終的に i=2 に来る値を考える。

2を含むサイクルが、2856492... の長さ l=6 だったとする。
先ほどの制約から、今回は全ての値を取れるわけではない。Xrmodm に矛盾しないようにする必要がある。

2つのサイクル長のGCDが1なら、何回も周期を繰り返せば全ての値を回れるが、 そうで無い場合、例えば今回は gcd(m,l)=2 なので、rmodgcd(m,l)、 つまり 2mod2 と等しい操作回数しか選べない。

よって、候補は偶数回操作された (A2,A5,A4) の中から選ぶことになる。
この中の最小値を i=2 に持ってくればよい。

A4 が最小だったとして、これは今のサイクルで4回操作された値なので、X4mod6 という制約が加わる。

これを先ほどの制約とマージする。
中国剰余定理を使うと、「m1 で割って r1 余り、m2 で割って r2 余る数 X は、lcm(m1,m2) で割って○○余る」の○○を求められる。
今回の場合、X10mod12 となる。

このようにして、前から「今の制約内で選択できる Ai の最小値」を求め、 「その Ai を希望の位置に持ってくる操作回数の制約を、今の制約とマージする」を繰り返せばよい。

問題となるのが、LCMを繰り返すと、例えばサイクル長が全て異なる素数だった場合に m がどんどん巨大になる。
実際に「総和が N2×105 を超えない範囲での、素数のLCM=総積」を求めると、700桁くらいの数になる。

確かに大きいが、まぁ、多倍長整数を扱える言語なら致命的な水準ではなく、そのまま扱って問題ない。
おこなう演算にもよるが、桁数が万まで来ると数秒では無茶かなというイメージ。

Python3

programming_algorithm/contest_history/atcoder/2024/0914_abc371.txt · 最終更新: 2024/09/20 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0