geam1113’s diary

主にAtcoderのコンテストの備忘録を書きます。AtCoder緑と水色を行ったり来たりしています。

AtCoder Beginner Contest 255 参加記録

コンテスト中AC:A
惨敗でした。コンテスト後ですが、自力でEまでは解けました。

2022/07/14 E問題の赤字部分がiになっていたので、jに修正

B - Light It Up

半径Rの2乗R^2を二分探索します。
2乗しているのは、long doubleなどの実数だと誤差が怖いためです。

二分探索では、以下を判定基準にします。

  • 全ての人がA_1,\cdots ,A_Nのいずれかの人が持つ明かりに照らされる

これはO(NK)で全探索できますが、コンテスト中はこの判定の実装が間違っていました。

R^2の上限として、10^{18}を見積もっても、十分余裕がありました(厳密には2\times 10^{10}くらい?)。

最後に2乗根をとって答えとします。
https://atcoder.jp/contests/abc255/submissions/32422863

C - ±1 Operation 1

等差数列S=\{a_1,...,a_N\}とします。
Xは、Sの最も近い要素にするのが最適です。
値の大小順にXSに挿入すると以下の3通りが考えられます。

  1. X,\ a_1,\ \cdots ,\ a_N
  2. a_1,\ \cdots ,\ a_l,\ X,\ a_r,\ \cdots ,\ a_N
  3. a_1,\ \cdots ,\ a_N,\ X

それぞれのケースの答えは、

  1. |X-a_1|
  2. min(|X-a_l|,|X-a_r|)
  3. |X-a_N|

です。
1,3のケースは簡単に求められます。問題は2のケースですが、これは二分探索により求められます。

コンテスト中はD\lt 0のケースが抜けていました。
Submission #32422643 - Aising Programming Contest 2022(AtCoder Beginner Contest 255)

D - ±1 Operation 2

例えば、AX未満の要素がB_1,\ B_2,\ \cdots ,\ B_mであったとします。

これをXにしたい時にかかる操作回数は、

X - B_1 + X - B_2 + \cdots + X - B_m

式変形すると、

X+X+\cdots + X - (B_1 + B_2 + \cdots + B_m)

となり、S_B = B_1 + B_2 + \cdots + B_mとすると、

X\times m - S_B

となります。

同様に、Xより大きいAの要素をC_1,\ C_2,\ \cdots ,\ C_nとすると、必要な操作回数は、

S_C - X \times m

となります。

余談ですが、式は説明のために書きましたが、コンテスト中は棒グラフを書いて、Xで線を引く感じをイメージしました。

解法ですが、X未満とXより大きい要素の和を効率よく得られればよいです。Aの順序は無視できるため、Aを昇順ソートして、累積和を計算しておくと、二分探索して和を得られます。
具体的には以下のようになります。

  1. Aを昇順ソートする
  2. Aの累積和Sを得る
  3. 各クエリについて、以下を行う
    1. 二分探索で、ソート済みのAの要素の内X未満の最大値A_sと、Xより大きい要素の最小値A_tを得る。
    2. X\times s - S_s + S_N - S_{t-1} - X\times (N-t)を出力する

なお、A_s,A_tが存在しない場合の例外処理が必要です。

コンテスト中はクエリの処理部分を以下のようにしていました。

X以上の最小値A_sを二分探索で得る(lower_bound)。
X\times s - S_s + S_N - S_{s} - X\times (N-s)を出力する

A_1,\ \cdots,\ A_sA_{s+1},\ \cdots ,\ A_Nに分けるような方法です。

この実装は、AXが存在すれば、おそらく問題なく動作すると思うのですが、そうで無い場合、例えば以下のようなケースで答えがおかしくなります。
A=\{1,5\},\ X=3

1,5を3にするのにそれぞれ2回の操作が必要なので、計4回となるはずです。しかし、上記の実装だと、A_s=5となり、3\times 2 - (1+5)=0となってしまいます。

おそらくA_sを大きい方の和に入れておけばよかったです。つまり、
A_1,\ \cdots,\ A_{s-1}A_{s},\ \cdots ,\ A_N

という風に分けておけばよかったものと思います。

Submission #32420079 - Aising Programming Contest 2022(AtCoder Beginner Contest 255)

E - Lucky Numbers

コンテスト後に解きましたが、思わぬところでTLEになり解決に時間がかかりました。

A_iA_1S_0,\ \cdots ,\ S_{i-1}で表現できます(便宜上、S_0 = 0とします)。具体例を示すと、

A_1 = S_0 + A_1
A_2 = S_1 - S_0 - A_1
A_3 = S_2 - S_1 + S_0 + A_1

という感じです。 ここで、定数部分をV_iとします。
そうすると、A_iは以下のように表現できます。

  • iが偶数の時、A_i = V_i + A_1

  • iが奇数の時、A_i = V_i - A_1

また、V_iは、以下の漸化式で計算できます。

V_i = S_{i-1} - V_{i-1}

ここまでで前準備が終了です。

少なくとも1つはラッキーナンバーにしないと損をするので、A_1,\ \cdots ,\ A_Nがそれぞれ、X_1,\ \cdots ,\ X_Mである時のラッキーナンバーの数を全探索します。

A_j = X_iである時を考えます。
A_1は以下のように計算できます。

  • \color{red}{j}が偶数の時  A_1 = X_i - V_j

  • \color{red}{j}が奇数の時  A_1 = -(X_i - V_j)

A_1が求まると、A_mがラッキーナンバーX_kとなるためのV_mが満たすべき条件がわかります。すなわち、

  • mが偶数の時 V_m  = X_k - A_1

  • mが奇数の時 V_m  = X_k + A_1

従って、予めV_1,\ \cdots ,\ V_Nについて、偶奇を分けてハッシュマップに個数をカウントしておけば、上記を満たすようなものの個数をO(1)で得られます。

A_j = X_iのときに、X_1,\ \cdots ,\ X_MとなるものがあるかをO(1)で調べられるので、時間計算量はO(NM^2)となり、問題ないと思われました。

しかし、実装時C++のunoredered_mapでこれを実装するとTLEになりました。

結局、V_iとその個数をpairにしたvectorを昇順ソートし、lower_boundすることでACしました。
Submission #32478157 - Aising Programming Contest 2022(AtCoder Beginner Contest 255)