geam1113’s diary

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

AtCoder Beginner Contest 285 メモ

コンテストページ:AtCoder Beginner Contest 285 - AtCoder

コンテスト中AC:A〜D
コンテスト後自力AC:E,F

D - Change Usernames

ユーザ名の重複を取り除き、番号を振り直したものをs=\{s_0,\ s_1,\ \cdots\ ,\ s_m\}とします。

s_iからs_jに、s_jからs_kに変更することを考えます。
配列の途中に要素を挿入する場合に末尾要素から順に後ろにずらしてスペースを作りますが、同じように、s_js_kに変更してからs_is_jに変更すれば、2つの変更を達成できます。

達成できない場合を考えると、例えば、s_iからs_jに、s_jからs_kに、s_kからs_iにする変更は不可能です。

これらの関係を有向グラフの問題にすると見通しが良くなります。

sの各要素を頂点、s_iからs_jに変更したい場合にs_iを始点、s_jを終点として有向辺を張ります。ユーザ名の変更は、有向辺を一回辿ることと考えることができます。

この有向グラフに閉路があると、移動順を決められず、達成できないことがわかります。逆に閉路がない場合、トポロジカルソートして、末尾から順に移動すれば達成できます。

従って、このグラフに閉路存在するかを判定すればよいです。
コンテストでは、AtCoder Libraryの強連結成分分解を使いました。
実装: Submission #38053494 - AtCoder Beginner Contest 285

E - Work or Rest

連勤する日数に対して、得られる生産量は固定です。

例えば3連勤すればA_1+A_2+A_1が生産量です。
これは、Aの累積和をとっておき、連勤する日数の偶奇で場合分けすることでO(1)で計算できます。

この問題では、以下のように考えることができます。

i連勤に必要な日数をw_ii連勤で得られる生産量をv_iとする。合計日数がN以下となるように選んで連勤する時、得られる生産量の最大値を求めよ。但し、各連勤は何度してもよい。

これは個数制限の無いナップサック問題と同じです。連勤日数は最大でN-1日なので、O(N^2)で解けます。

注意が必要なのは、i連勤に必要な日数はi日ではないということです。
前か後ろの一方にのみに休日を入れておけば全体として矛盾がないので、i+1日としておけば良いです。
実装: Submission #38131229 - AtCoder Beginner Contest 285

F - Substring of Sorted String

S_l,\ S_{l+},\ \cdots ,\ S_rを、S_{l,r}と表現します。

S_{l,r}Tの連続する部分列であるための条件は以下の2つです。

  • S_{l,r}が昇順に並んでいる
  • 文字をabc順に並べた時にS_{l}より後ろ、かつ、S_rより前の文字c_iについて、S_{l,r}に含まれるc_iの個数とS全体に含まれるc_iの個数が等しい

1つ目は自明だと思うので、2つ目を具体例で示します。

S_{l,r}=aabccdfとします。
まず、S_l = a,\ S_r = fS_{l,r}の先頭と末尾ですが、これはSに何個含まれていても良いです。例えば、Tの連続する部分列が、

aaabccdfffff

であったとしても、

a|aabccdf|ffff

という感じに分けることを考えればS_{l,r}に一致させることができることがわかります。

反対にafの間の文字、b,c,d,eS_{l,r}に含まれる個数とS全体に含まれる個数が一致しなければいけません。
もし、S_{l,r}以外の箇所にこれらの文字が含まれていた場合、昇順ソートしてTにした時に、S_{l,r}の間に1文字以上挿入されることになり、一致しなくなります。

次に、これらの計算方法です。
まず、昇順に並んでるかどうかですが、これは順序付き集合Uで、以下を管理します。

  • Sの連続する部分列であって、その部分列が昇順であるように(最長に)分割した時の、各部分列の開始位置

例えば、S=aabcdacfdaは、aabcd|acf|d|aと分割できるので、U=\{1,6,9,10\}となります。

l,rが与えられたとき、U上を二分探索し、l及びrより大きい最小の整数(upper_bound)が一致すれば、S_{l,r}は昇順です(実装では、\inftyUに入れておけば扱いやすいです)。

文字変更クエリでは、Uの中身を変更していきます。変更される可能性があるのはx,x+1の2つです。文字変更によって、新たに昇順になったか、または昇順でなくなったか、に応じて、Uからx,x+1を追加または削除すればよいです。

値の追加、削除、二分探索が可能なデータ構造(C++ならset)を用いれば各クエリでO(logN)で処理できます。

次に、2つ目の条件ですが、これは値の1点更新を伴う区間和を処理できればよいので、Fenwick Treeで対応できます。

なお、2個目のクエリで全ての文字について、その個数を求めたとしても、文字種が26しかないので計算量としては問題ないです。

実装: Submission #38099915 - AtCoder Beginner Contest 285