AtCoder ABC212

ABC212参加しました。

atcoder.jp

 

A - Alloy

解説 - AtCoder Beginner Contest 212

A,Bの値によって、そのままifを書き下せばok。

B - Weak Password

解説 - AtCoder Beginner Contest 212
提出 #24656509 - AtCoder Beginner Contest 212

要素は2つ。これら2つのうちどちらか片方を満たしていればWeakです。

  1. 数字がすべて同じ
  2. 数字が連番である

1.はそのまますべての桁が同じかどうかでみればよいです。
2.も(9の場合だけ気をつければ)そのまま書き下します。

各桁の数字は10で割ったあまりを見ていけば、下の桁から取り出すことができます。
詳しくは自分の提出コードをどうぞ。

C - Min Difference

解説 - AtCoder Beginner Contest 212
提出 #24663995 - AtCoder Beginner Contest 212

平たくいえば、数列A,Bそれぞれから数字を一つ選んだときに、その差が最小になるときの値を求める、ということになります。

ほぼ解答はユーザー解答と同じになりました。

そのままやると、A,Bから要素を全探索することになります。これはTLE。

なので、Aから抜き出した数字$A_i$に対して、Bの中で最も差が小さくなる数字$B_k$を選び出します。この差が小さくなるということは、$A_i$に近い数を探し出せばいいということです。

これを効率よくやるために、まずBをソートします。 (自分はAもやっちゃいましたが、Bだけでいいはず) 後の2分探索の準備でもあり、この問題ではソートしても答えは変わりません。

ソート後、C++なら、LowerBoundを使えばいいです。他の言語にない場合は、似たものを実装してライブラリ化しておくといいでしょう。

LowerBoundをソートされたBに使えば、Bの中にある最も$A_i$に近い値のインデックス$k$をとってこれます。 ただしこれで取れるのは、$A_i$以上の値です。

実際は、Bの中の$A_i$に近い値は、$A_i$より小さい場合もありえます。
例えばB=(1,10000)で$A_i$が10だったら、LowerBoundすると10000を指しますが、近いのは1です。

これを解決するため、LowerBoundで取れた$k$から一つ小さい$k - 1$も、$A_i$に近い値の候補として取ってくればいいです。

詳細はコードを見てください。

D - Querying Multiset

解説 - AtCoder Beginner Contest 212
提出 #24704348 - AtCoder Beginner Contest 212

PriorityQueueを使います。

ネックになっているのは次の2つの操作です
操作2 : 袋の中の玉の値にXを足す
操作3 : 袋の中の玉の最小値を取り出す
どちらもすべての玉を見ないといけないため、計算量は$O(Q)$であるように思えます。これがクエリ数Q回あるので、総計算量$O(Q2)$でTLEです。

これらの操作を高速化します。

まず操作3ですが、これはPriorityQueueを使えば解決します。
内部でツリー上に要素を管理し、要素の追加が$O(logN)$、取り出し時に(PriorityQueueの実装にもよりますが)常に最小の値を取り出す操作$O(logN)$が可能です。実装によるというのは、PriorityQueueがMax-heapかMin-Heap実装のどちらかによるということで、どちらの場合でも計算量は変わりません。また言語によっては標準でついてないかも知れません。今回は前から準備してあったPriorityQueue(C#版)を使用してます。これを使う以上、操作1の玉の追加もただ追加するよりは少し時間を取りますが、$O(logN)$なので許容範囲でしょう。

次に問題の操作2ですが、これはそもそも操作2が来るたびに値を足し込むことが問題です。答えに直結するのは、操作3で値を取り出したときに値をメモしておくことなので、

  • 操作2が来る度に足し込む値deltaを覚えておく(何度も操作2が来たら、deltaにさらに足し込む)
  • 操作3が来たら、袋から最小値を取り出し、値をメモする前にdeltaを足し込む

ことで、操作2の全玉の探索をなくすことができます。

ただし、これだと、以下の場合に問題になります

操作 X
1 1
1 2
2 100 ※1
1 3 ※2
3 なし ※3

※1の実行後の状態は以下です。
玉=(1,2) ただしdelta=100

※2の実行後の状態は以下です。
玉=(1,2,3) ただしdelta=100

このまま※3を実行すると、最小値として欲しいのは3ですが、これにdeltaが足されるために、実際にメモされる値は103となります。操作2の後に玉3が入ったので、本来deltaは足される必要はないはずです。また、deltaを足すことで、操作3の目的である「最小値を取り出す」を達成できていません。

よって※2の操作1実行時に予め3からdeltaを引いておけば、次の※3の段階で再びdeltaが足されるため、+-0で帳尻が合います。

後は提出したコードを御覧ください。

まとめ

DはPriorityQueueだという事はわかってもその後の工夫ができなかった... せっかく以前ググりまくって用意したのに~