AtCoder Beginner Contest 285 メモ
コンテストページ:AtCoder Beginner Contest 285 - AtCoder
コンテスト中AC:A〜D
コンテスト後自力AC:E,F
D - Change Usernames
ユーザ名の重複を取り除き、番号を振り直したものをとします。
からに、からに変更することを考えます。
配列の途中に要素を挿入する場合に末尾要素から順に後ろにずらしてスペースを作りますが、同じように、をに変更してからをに変更すれば、2つの変更を達成できます。
達成できない場合を考えると、例えば、からに、からに、からにする変更は不可能です。
これらの関係を有向グラフの問題にすると見通しが良くなります。
の各要素を頂点、からに変更したい場合にを始点、を終点として有向辺を張ります。ユーザ名の変更は、有向辺を一回辿ることと考えることができます。
この有向グラフに閉路があると、移動順を決められず、達成できないことがわかります。逆に閉路がない場合、トポロジカルソートして、末尾から順に移動すれば達成できます。
従って、このグラフに閉路存在するかを判定すればよいです。
コンテストでは、AtCoder Libraryの強連結成分分解を使いました。
実装: Submission #38053494 - AtCoder Beginner Contest 285
E - Work or Rest
連勤する日数に対して、得られる生産量は固定です。
例えば3連勤すればが生産量です。
これは、の累積和をとっておき、連勤する日数の偶奇で場合分けすることでで計算できます。
この問題では、以下のように考えることができます。
連勤に必要な日数を、連勤で得られる生産量をとする。合計日数が以下となるように選んで連勤する時、得られる生産量の最大値を求めよ。但し、各連勤は何度してもよい。
これは個数制限の無いナップサック問題と同じです。連勤日数は最大で日なので、で解けます。
注意が必要なのは、連勤に必要な日数は日ではないということです。
前か後ろの一方にのみに休日を入れておけば全体として矛盾がないので、日としておけば良いです。
実装: Submission #38131229 - AtCoder Beginner Contest 285
F - Substring of Sorted String
を、と表現します。
がの連続する部分列であるための条件は以下の2つです。
- が昇順に並んでいる
- 文字をabc順に並べた時により後ろ、かつ、より前の文字について、に含まれるの個数と全体に含まれるの個数が等しい
1つ目は自明だと思うので、2つ目を具体例で示します。
とします。
まず、はの先頭と末尾ですが、これはに何個含まれていても良いです。例えば、の連続する部分列が、
であったとしても、
という感じに分けることを考えればに一致させることができることがわかります。
反対にとの間の文字、はに含まれる個数と全体に含まれる個数が一致しなければいけません。
もし、以外の箇所にこれらの文字が含まれていた場合、昇順ソートしてにした時に、の間に1文字以上挿入されることになり、一致しなくなります。
次に、これらの計算方法です。
まず、昇順に並んでるかどうかですが、これは順序付き集合で、以下を管理します。
- の連続する部分列であって、その部分列が昇順であるように(最長に)分割した時の、各部分列の開始位置
例えば、は、と分割できるので、となります。
が与えられたとき、上を二分探索し、及びより大きい最小の整数(upper_bound)が一致すれば、は昇順です(実装では、をに入れておけば扱いやすいです)。
文字変更クエリでは、の中身を変更していきます。変更される可能性があるのはの2つです。文字変更によって、新たに昇順になったか、または昇順でなくなったか、に応じて、からを追加または削除すればよいです。
値の追加、削除、二分探索が可能なデータ構造(C++ならset)を用いれば各クエリでで処理できます。
次に、2つ目の条件ですが、これは値の1点更新を伴う区間和を処理できればよいので、Fenwick Treeで対応できます。
なお、2個目のクエリで全ての文字について、その個数を求めたとしても、文字種が26しかないので計算量としては問題ないです。
AtCoder Beginner Contest 282 備忘録
コンテストページ: HHKB Programming Contest 2022 Winter(AtCoder Beginner Contest 282) - AtCoder
コンテスト後にAC: A〜C
コンテスト後にD,Eは自力で解けず 。Fは自力AC
D - Make Bipartite 2
コンテスト中はよくわからない勘違いでACできませんでした。
まず、連結成分に2部グラフでないものが含まれていた場合、辺追加後のグラフも2部グラフにならないので0です(これが抜けていました)。以下、全ての連結成分が2部グラフであるとします。
まず、連結成分内同士を繋ぐ場合の辺の本数を求めます。
連結成分の1つについて、頂点を2つの独立集合とに分けます。これは頂点を2色彩色した場合に同色となる頂点集合と同じです。
または内で辺を繋ぐと2部グラフでなくなります。との頂点同士はどのように繋いでも2部グラフのままです。
の任意の頂点について、の全ての頂点と辺を繋ぐことが可能なので、合計本の辺を繋ぐことができるわけですが、既に辺が存在する場合はその分を引かなければなりません。
つまり、は本の辺を繋ぐことができます。但し、はから出ている辺の本数とします。
従って、連結成分内で繋げる辺の本数はについて、で繋げる辺の本数の総和を求めればよいです。
次に、連結成分間です。
任意の連結成分間では、全ての頂点間の辺を繋ぐことができます。もし、頂点を2色彩色した場合の同色同士を辺で繋いだとしても、一方の連結成分の色を全て反転させればよいです。
よって、連結成分の頂点数の合計がであり、の頂点数がだとすれば、本の辺を繋ぐことができます。
以上から、連結成分間について、連結成分の頂点数の和を順次計算しながら、辺の本数を求めていくことができます。
実装: Submission #37366599 - HHKB Programming Contest 2022 Winter(AtCoder Beginner Contest 282)
E - Choose Two and Eat One
公式解説を見ました。
こういう問題はどうやったら気づけるのか難しいところです。
AtCoderの過去問 (ネタバレ注意)で、似たような感じの木で考える問題もありました。これも葉から順に操作していくというものでした。
1つずつ選んでいく感じの問題は木を疑ってみると良いのかもしれません。
F - Union of Two Sets
公式解説そのままだったので、解法の詳細は省略し、解法に至った経緯だけメモしておきます。
まず、全ての区間の組を得ようとした場合、個必要です。ワーストケースのの場合だけを考えても、という制約を越えるため、全ての区間を列挙することは不可能です。
そこで、例えば、区間の数を個に落とすことができれば、解決できそうです。
そんなわけで、区間の長さを2分の1ずつ小さくすることを考えます。
N=10を例にします。10/2=5なので、長さが5の区間について考えます。 全ての長さ5の区間をカバーするためには、始点を1つずつずらす必要があります。そのため、
[1,5] [2,6] [3,7] [4,8] [5,9] [6,10]
が必要です。
これを眺めたところ、ここから適切に区間を2つ選ぶことで長さ5〜10の区間すべてをカバーできることがわかりました。
一般化すると、
また、各長さの区間の個数は個以下となることから、全ての区間を列挙しても個以下となります。
以上から、のすべての区間を列挙しておけば、クエリに答えられます。
クエリでは、区間の長さ以下となる最大の2の冪乗について、その指数が必要になります。
これは、ビットが1である最上位のビットが何番目かを得ればよいです(__builtin_clzが使えます)。
実装: Submission #37417443 - HHKB Programming Contest 2022 Winter(AtCoder Beginner Contest 282)
AtCoder Beginner Contest 281 参加記録
コンテストページ: https://atcoder.jp/contests/abc281
コンテスト中AC: A〜D
コンテスト後にEをAC(自力)
D - Max Multiple
- が小さい
- 倍数はの情報があれば良い
これらからDPだろうと判断しました。
情報としては、
- 何項目までを考慮したか
- 選んだ項の総和の
- 選んだ項の個数
が分かればよいので、
の項目までを考慮し、選んだ項の総和のがであり、選んだ項の個数がであるときの選んだ項の総和の最大値
遷移は以下のとおりです。
を選ばない場合
を選ぶ場合
初期値は、としておき、その他はなどで埋めておけばよいです。
なお、がの場合は遷移元としないようにする必要があることに注意します。
答えは、です。
提出コード: Submission #37160568 - AtCoder Beginner Contest 281
E - Least Elements
コンテスト中multisetで解こうとして結局だめでした。コンテスト後にWavelet Matrixで解けました。
からまでを昇順に並べた数列をとします。の先頭項の和をとします。
について、を削除し、を追加した場合に、からにどのように変化したかがわかれば、から変化分を増減させることで、を計算できます。
これを考える時、がそれぞれの番目以内かそうでないかの4通りを考えれば良いです。
具体的に説明します。
がの番目以内
この時、の分、和は減少します。がの番目以内
が削除され、が挿入することを考えると、削除されて空いた箇所に左詰めまたは右詰めで各要素が移動し、が挿入されたと考えることができます。
このケースでは、新たにが番目以内となってその分の和が増加するので、
と計算できます。がの番目より後ろ
が削除されて空いた箇所にそれより後ろの要素が左詰めされて、が挿入されます。
このケースでは、における番目の要素がで新たに番目となり、この分の和が増加するので、これをとおけば、
と計算できます。
がの番目より後ろ
このケースでは、による和の減少はありません。がの番目以内
が挿入されると、の削除によって空いた箇所に右詰めされるように動きます。 このケースでは、新たにが番目以内となってその分の和が増加し、で番目だった要素がにおける番目となって、この分が減少するので、
と計算できます。がの番目より後ろ
は和に影響しないので、
となります。
を全て記録することはの領域が必要となるので、不可能だと思います。また、仮にそれが可能だとしても、C++のvectorなら昇順ソートが毎回必要ですし、multisetでは何番目の要素かを得るのに時間がかかります。
そこで、Wavelet Matrixを使います。Wavelet Matrixはで構築し、で指定区間の任意のx番目に小さい要素を得ることが可能です。(但し、はに含まれる要素の最大値)
これで、で解くことができます。
AtCoder Beginner Contest 280 備忘録
コンテストページ:
コンテスト中AC: A〜E
D - Factorial and Multiple
制約上、くらいの解法、すなわち素因数分解や約数を利用した解法を疑います。今回は素因数分解で解けました。
が素因数を1つだけ持つとします。はの素因数を全て含む必要がありますが、は合成数ではないので、2つ以上の正整数の積で表すことができません。従って、少なくともは必要であり、であることが確定します。
が2個含まれる場合を考えると、の次にが現れるのはなので、が確定します。
一般化すると以下が言えそうですが、これは誤りです。
に素因数が個含まれる時、が成り立つ
反例として、があります。
2が3個含まれるので、が必要かといえば、そうではなく、で達成できます。
これはの倍数にが2個以上含まれることによって発生しています。
そこで、以下は成り立つと言えます。
に素因数が個含まれるとする。の順に探索し、の数が個以上に初めてなるものをとするとき、が成り立つ
これをに含まれる素因数全てについて求めていきます。
と素因数分解されたとすれば、各素因数について前述の値を求め、それらをとすると、
が成り立つ必要があるので、としてとりうる最小値はであり、これが答えです。
かなり大雑把に計算量を見積もります(間違っていたらすみません)。
素因数は2以上なので、素因数の個数の和は個以下です。よって、各素因数について各倍数を調べる部分は合計回以下です。また、各倍数に含まれる素因数の個数もを超えないはずなので、かなり大雑把に見積もってもで済みます。
そのため、おそらく素因数分解の計算量がボトルネックとなります。
素因数分解はあまり効率的ではない試し割りであっても、くらいかと思います。(素因数の個数が合計で個以下なので、同じ素因数で複数回割る部分を考慮してをつけましたが、不要かもしれません)
提出コード: Submission #36979663 - Denso Create Programming Contest 2022 Winter(AtCoder Beginner Contest 280)
E - Critical Hit
期待値DPの典型的な考え方で解けます。
状態が遷移元でその期待値をとする。
状態に遷移し、
遷移先の期待値がで、
遷移する確率がで、
遷移にかかる操作回数がの時、
今回の場合、体力がの状態からは、
体力がとの状態に遷移し、
遷移する確率が、で、
遷移にかかる操作回数が、いずれも回なので、
この式に従って計算すれば、メモ化再帰やDPなどで解けます。
なお、メモ化再帰ではとなる場合の処理について注意します。
提出コード: Submission #36983677 - Denso Create Programming Contest 2022 Winter(AtCoder Beginner Contest 280)
計算方法がフィボナッチ数列に似ているので、以下のように考えれば行列累乗で解けそうです。
AtCoder Beginner Contest 279 備忘録
コンテストページ: TOYOTA SYSTEMS Programming Contest 2022(AtCoder Beginner Contest 279) - AtCoder
コンテスト中AC:A〜D
コンテスト後にE,Fを自力AC。
D - Freefall
下に凸な関数っぽい感じだったので、下のサイトを参考に三分探索しました。
三分探索を救いたい - Qiita
誤差が出てくるので、出てきた操作回数±10くらいの範囲を探索しました。
一応、ACできましたが、疑問がいくつか残ったままだったので、コンテスト後に調べてみました。
整数の三分探索の方法
公式解説によれば、繰り返しの条件をr - l > 2として、終了後にrからlまでの最小値を答えれば良いようです。
おそらく、3分する点がlと重複しない間は探索を繰り返すものと思います。
探索範囲の最大値
ならとなるので解は以下となる、ということらしいです。詳細は公式解説参照。
下に凸であることの判定
上に凸,下に凸な関数と二階微分 | 高校数学の美しい物語
上記サイトによれば、二階微分が非負整数なら下に凸と判定できるそうです。
ざっくりとした理由をメモしておきます。
まず、一階微分は接線の傾きであり、下に凸の場合単調非減少となる必要があるそうです。確かに、下に凸の二次関数などを例に考えれば、最小値より前は傾きが負で、最小値に近づくにつれてだんだん傾きは小さくなり(0に近づき)、最小値で0になります。そして、その後は傾きは正になり、傾きは大きくなっていくことが想像できます。
そして、傾きが単調非減少になるためには、傾きの変化量は常に非負である必要があるので、二階微分が非負になるということが理解できます。
高校数学から大分遠ざかっていたので、微分を思い出しながら実際にやってみます。
• 一階微分
の項はになります。
次にを含む項です。
合成関数の微分を思い出すと、
のとき、
です。
を含む項は、
ですから、その微分は、
よって、
です。
となります。において、が成り立つので、下に凸であることが示されました。
提出コード: Submission #36819682 - TOYOTA SYSTEMS Programming Contest 2022(AtCoder Beginner Contest 279)
E - Cheating Amidakuji
このユーザ解説と同じ方法でした。
実装: Submission #36848977 - TOYOTA SYSTEMS Programming Contest 2022(AtCoder Beginner Contest 279)
F - BOX
公式解説と同じ方法でした。
実装: Submission #36835935 - TOYOTA SYSTEMS Programming Contest 2022(AtCoder Beginner Contest 279)
AtCoder Beginner Contest 278 備忘録
コンテストページ:AtCoder Beginner Contest 278 - AtCoder
コンテスト中AC:A〜D
E,Fはコンテスト後に自力AC
E - Grid Filling
このユーザ解説と同じ方法で解きました。
想定解法は二次元累積和だったようです。
実装:Editorial - AtCoder Beginner Contest 278
二次元累積和の構造体を作って解いてみました。
実装:Submission #36758941 - AtCoder Beginner Contest 278
F - Shiritori
後退解析+bitDP(巡回セールスマン問題)の考え方で解けます。
問題を有向グラフに言い換えておきます。
- を頂点とする。
- を言った後にを言える場合、頂点から頂点へ向かう有向辺があるとする。
- 有向グラフ上を交互に移動することを繰り返し、移動できなくなった方が負け
制約上、bitDPが疑われるので、その要領で考えてみます。
これまでに訪問した頂点集合がで頂点で自分のターンを終えたとき、勝てるなら1、負けるなら0
まず、"次のターンでこれ以上移動できる頂点がない"となった時点で勝ちが確定します。そこで、訪問した頂点数が多い順から勝敗を確定していけそうです。
これが後退解析にあたります(おそらく)。
では、勝敗の確定条件を考えます。
自分が頂点で移動を終え、から移動可能であって、かつ未訪問の頂点がであるとします。
このうち、いずれか1つでも勝ちとなる頂点が存在する場合、相手はその頂点に移動すれば勝てるので、自分は負けとなります。逆に負けの頂点しかなければ、勝てます。
また、訪問可能な頂点がない場合も自分が勝ちとなる条件となります。
後はこれに従って、bitDPを逆に回していきます。
最終的に"頂点が1つだけ訪問済みである"という状態のDP値(例えば、N=3ならdp[001][0], dp[010][1], dp[100][2]。なお、1つ目のインデックスはbit列)について、頂点のうち、ただ1つでも勝ちがあれば太郎くんが勝ちます。
実装: Submission #36721268 - AtCoder Beginner Contest 278
余談で、DPの別の言い方を示しておきます。
これまでに訪問した頂点集合がで頂点で自分のターンが回ってきた時、勝てるなら0、負けるなら1
この場合、移動できなくなったら負けるので、訪問可能な頂点がなければ負けです。また、相手のターンになったときに負けとなる頂点が1つでもあれば自分は勝てます。
最終的に"頂点が1つだけ訪問済みである"という状態のDP値について、頂点のうち、ただ1つでも負けがあれば、太郎くんが勝ちます(1つだけ訪問済みの状態で順番がくるのは次郎くんであるため)。
実装:Submission #36708784 - AtCoder Beginner Contest 278
AtCoder Beginner Contest 277 備忘録
コンテスト中AC:A〜E
D - Takahashi's Solitaire
コンテスト後に考え直したので、メモしておきます。
一旦modを無視します。
以下の問題を解けばこの問題に答えられます。
から要素を順に選び、とする。但し、は以下の条件を満たさなければならない。
について、
要素の和が最大となるを求めよ。
ここで、を昇順ソートしておくと、はの連続部分列となります。
また、和を最大化したいので、連続部分列は条件を満たす限り伸長させた方が良いです。そのため、各要素は2つ以上の連続部分列に属する必要がありません。
そこで、の連続部分列を探索する際、例えば、が可能な限り伸長した連続部分列であるなら、次の連続部分列の探索はから再開すれば良いです。 探索範囲を右に進めるだけとなるので、となります。
modの部分ですが、これはかつの場合に、これらを含む連続部分列を繋げるようにすれば解決になるわけですが、以下の2パターンで実装してみました。
解決方法1
配列が円環となることが想定される場合の常套手段ですが、を昇順ソートしたものを2つ繋げます。これに対して連続部分列を探索すれば、modを考慮した場合と同様の結果が得られます。
但し、にの全てが含まれる場合は、の要素の和を超えてしまうので注意が必要です。
実装例:Submission #36484745 - Daiwa Securities Co. Ltd. Programming Contest 2022 Autumn (AtCoder Beginner Contest 277)
解決方法2
Union-Findを用いた解法です。0-indexedとします。
を昇順ソートし、について、または、が成り立つなら、Union-Findで同一連結成分とします。
Union-Findにおける連結成分の和を計算していけば、答えを得ることができます。
E - Crystal Switches
解法はこちらの公式解説とほぼ同じなので、省略します。
実装では、を、頂点でスイッチの状態がの時の移動回数の最小値、としました。
また、スイッチを押す操作もダイクストラ法で頂点からに移動する際に考慮されるようにしました。
提出コード:Submission #36446787 - Daiwa Securities Co. Ltd. Programming Contest 2022 Autumn (AtCoder Beginner Contest 277)
スイッチの状態の表現が公式解説と逆なので、ご注意ください。