ARMERIA

Rubyと競技プログラミングの話 AtCoderやCodeforcesの問題解説記事が多め。

Codeforces Manthan, Codefest 18 参加記録

何か不規則な解き方してますが、Fが通ったおかげでレートも結構上がりました。Dは誤読で落ちました(にゃーん)

f:id:betrue12:20180903203023p:plain

後から解いたものも含めてA~Fを振り返ります。

A. Packets

Problem - A - Codeforces

問題概要

1円玉  n 枚をいくつかの袋に入れて、「袋をいくつか適切に選ぶことで、合計金額  0, 1, 2, ..., n*1の全通りが作れる」状態にしたい。最小の袋の数を求めよ。

解法

2進数で作るとよさそうです。 2^{m} -1 \le n である最大の整数を  m として、 m 個の袋に  1, 2, 4, ..., 2^{m-1} 円を入れると、  0, 1, 2, ..., (2^{m}-1) 円の全パターンが作れます。端数が出た場合は、余った分をもう1つの袋に入れれば、  n まで全部のパターンを作れます。つまり袋の数は、  2^{M} - 1 \ge n であるような最小の整数  M となります。構成方法を示したので、これは十分条件です。

次に必要性、つまりこれより小さい解がないことを確かめます。  M 個の袋を使って得られるパターン数は、いくら値が被らないように努力したとしても、全ての袋について「選ぶ」「選ばない」で分岐した高々  2^{M} 通りです。作る必要があるパターン数は  n+1 通りなので、  2^{M} \ge n+1 が必要条件となります。これは上の式に一致するので、ちょうど必要十分です。

ACコード

B. Reach Median

Problem - B - Codeforces

問題概要

素数 n (奇数)の数列  a_{1}, ..., a_{n} と整数  s が与えられる。操作「数列の要素  a_{i} を1つ選び、+1または-1を足す」を繰り返して数列の中央値を  s にしたい。操作の最小回数を求めよ。

解法

素数が奇数の数列の中央値が  s になる条件は、要素を左から右に昇順ソートしたときに真ん中の要素が  s であることです。もう少し全体的に見ると、

  • 真ん中の要素が  s である
  • 真ん中より左の要素が  s 以下である
  • 真ん中より右の要素が  s 以上である

このようになっていればよいです。

元の数列を昇順ソートして、上記の条件を満たすように加減してやればよいです。わざわざ大小の順番を入れ替えるような操作は、得になることはありません。

ACコード

C. Equalize

Problem - C - Codeforces

問題概要

長さ  n で各文字が0か1の文字列  a, b が与えられる。 a に以下の操作を好きな順序で好きな回数繰り返して、  b と一致させたい。最小のコストを求めよ。

  •  a の文字を1つ選んで書き換える。コスト1。
  •  a の文字を2つ選んで入れ換える。コストは入れ換える2文字の距離。

解法

まず、「入れ換える」ことで得になるケースはかなり限られています。入れ換える操作は、最大2文字を書き換える操作をすれば同じことができて、そのコストは最大でも2です。つまりコストが2以上かかるような入れ換え操作はする必要がなくて、

  • 隣り合った2文字を両方書き換える(コスト2)代わりに、その2文字を入れ換える(コスト1)

以外の入れ換え操作では得をしないことがわかります。そのため、

  •  a 側が「10」で  b 側が「01」、あるいはその逆である箇所のみ、入れ換え操作を行う。
  • それ以外の場所は単純に書き換え操作を行う。

これで最適となります。

ACコード

D. Valid BFS?

Problem - D - Codeforces

問題概要

 n 頂点の木と、  1, ..., n を並び替えた数列  a_{1}, ..., a_{n} が与えられる。数列が「木を頂点1からBFSしたときの探索順序」になっているか判定せよ。

解法

問題文にも書いてありますが、BFSを実装する際は

  1. 始点をキューに入れる。
  2. キューから頂点番号を取り出す。
  3. その頂点に隣接する頂点のうち未訪問のものをキューに入れる(普通のBFSでは、このときのpush順序は実装者が勝手に選ぶ)。
  4. 2に戻る。

を、キューが空になるまで続けます。

今回は始点と探索順序が指定されているので、

  1. まず数列の始点  a_{1} をキューに入れる。ただし、これが1でない場合は No で終了。
  2. キューから頂点番号を取り出す。
  3. 数列で指定された順に、その頂点に隣接する頂点のうち未訪問のものをキューに入れる。このとき、指定された次の番号が隣接する頂点として取れない場合、順番が守れないので No で終了。
  4. 2に戻る。

これをキューが空になるまで完遂できれば Yes です。

本番では太字にしたところを完全に読み忘れていました。これでプレテスト通るのか…

ACコード

E. Trips

Problem - E - Codeforces

問題概要

 n 人の人がいて、以下を  m 回繰り返す。 m 回それぞれについて、2. の答えを出力せよ。

  1.  x_{i} y_{i} が友達になる。(友達になるペアは毎回異なる)
  2. 「グループ内に少なくとも  k 人友達がいる」を全員が満たしているようなグループを作る。そのうち最も人数の多いものを選び、その人数を出力する。

解法

友達がどんどん増える優しい世界ですが、これだと考えにくいので、時間を逆回しして友達がどんどん減っていく設定にします。(悲しいですね)

人を頂点、友達関係を辺とするグラフを考えます。まず最後の状態を考えるため、全ての辺を追加してしまいます。これ以降、辺が増えることはありません。このとき、

  1. 友達が  k 人未満の人は、絶対にグループに入れないので除外する。
  2. 他の人が除外された結果、友達が  k 人未満になってしまった人も除外する。

これを繰り返すと、最後に残った人たちは  k 人以上友達がいるため、全員でグループを組むことができます。

あとは、時間を巻き戻すごとに辺が1つずつ消えていくので、その2人について上記の1. の判定を行い、2. の判定を伝播させていけばよいです。

どれだけ伝播するかで1回1回の判定回数にはばらつきがありますが、必ず「辺を切る」という操作を伴うため、問題全体での判定の合計回数は、高々辺の数の定数倍になります。

ACコード

F. Maximum Reduction

Problem - F - Codeforces

問題概要

ちょっと問題文の読解が難しいので図にします。

f:id:betrue12:20180904003852p:plain

素数  n の数列と整数  k が与えられ、図のように「連続する  k 要素の最大値」を取っていく操作を繰り返します。要素数 k 未満になったら操作終了です。

この図では  n = 5, k = 3 です。(問題文中の2つ目のサンプル)

この操作でできる、2段目以降の数列の合計値を求めよ、という問題。

解法

この操作では、大きい数字がずっと残り続け、大きい数字の近くにある数字はmaxを取る時に消えていきます。

もちろん一番大きい数字は最後まで残るのですが、それ以外の数字になる場所だけを見てみましょう。

f:id:betrue12:20180904004501p:plain

最も大きい数字「9」以外の要素は、1段目4つ・2段目2つ。これは、「最初から1段目が左側の4要素しかなかったとしたら」と考えた時の、操作結果と全く同じになります。

一番大きい要素の位置が端っこでない場合はどうでしょうか。

f:id:betrue12:20180904005100p:plain

少しごちゃごちゃしていますが、一番大きい「9」で分断された左右が、やっぱりそれぞれ問題で与えられた操作結果と同じになっています。

ここから、「一番大きい数字で分断して再帰的に処理していく」という方針が立ちます。必要な道具は、

  1. 任意の区間内での数列の最大値と、その位置を効率的に取得する。
  2. その最大値が、2段目以降の数列全体で合計いくつ存在するかを効率的に求める。

の2つです。

1についてはセグメント木が使えます。値と位置をペアにして格納しておけば位置も取れます。

2についてですが、上図の「9」の配置なんかを見ると不規則に見えます。ので、ちょっと計算をサボって「一度全部のマスで足してしまって、残りを差分更新することで調整する」という方法を取ります。

f:id:betrue12:20180904010056p:plain

図のように差分で計算をしていけば、1度の計算で扱う範囲は3角形(場合によっては台形)のような形になり、等差数列の和の公式で数を求められます。

解法をまとめると、

  1. 区間内の最大値とその箇所をセグメント木で求める。
  2. 今考えている区間から発生する数列の要素数の合計を、等差数列の和の公式で求める。
  3. 個数×(今回の最大値 - 前回の最大値)を答えに加算する。
  4. 最大値の箇所で区間を分割し、再帰的に処理する。

これで解けます。

配点高めですが、類題の経験があったので意外とサクっと解けたような気がします。

ACコード

さいごに

f:id:betrue12:20180904011157p:plain

今回のコンテスト後のレートが1892。誤読しなければ紫になっていたと思うと虚しいですが…これもこどふぉ。問題はちゃんと読みましょう。

脚注

*1:元問題の記載では1円からですが、「袋を1つも選ばない=0円」を含めたほうが説明の都合がよいのでそうしています。