ABC246 参加記録
コンテスト中AC:A〜C,E
D,Fも自力ACできたので、書きます。
コンテスト全体の感想
Dが思いつかなかったので、Eを先に解きました。Eは解法自体はわかったのですが、実装に時間がかかってしまいました。
Fで残り時間10分くらいで、包除原理はわかったのですが、実装は間に合いませんでした。コンテスト後に解きましたが、最初から作られる文字列の数の計算方法が間違っていました。
Dのように変数か複数ある場合、どれかを固定するという考え方が大事ですね。(一方は全探索できて、片方が決まれば、他は高速に計算できる場合がある)
D - 2-variable Function
の上限はで、全探索可能です。を固定すると、に対しては狭義単調増加です。よって、となるようなを二分探索できます。
E - Bishop 2
マスを4倍に拡張し、左上、右上、左下、右下への移動中を表す状態を追加します。具体的には、
マスにいて状態がの時の最短距離
とします。
但し、とし、
の時、左上に移動中
の時、右上に移動中
の時、左下に移動中
の時、右下に移動中
とします。
まず、同じマス内ではコスト1で方向転換ができます。(同じ向きになるのは無駄ですが、実装が楽です。) 従って、について、
次に、に応じて、以下の通りに進行方向の次のマスにコスト0で動けます。
の時、左上に進める。
の時、右上に進める。
の時、左下に進める。
の時、右下に進める。
初期値は、既に1回目の移動が始まっていることにする必要があるので、について、
です。
これをダイクストラ法で解けば良いです。
答えは、について、の中の最小値です。
なお、到達可能かの判定が必要なことに気をつける必要があります。
実装ですが、AtCoderの過去問にも同様の問題があったので、その時の公式解説の実装方法に倣っています。
マスの状態の頂点番号を
id = 4(i+j*N)+k
とします。
idからi,j,kを得たい時は、C++なら、
i = id/4%N
j = id/4/N
k = id%4
とすれば良いです。
このようにするメリットは、通常のダイクストラ同様に、(距離, 頂点番号)という組だけで管理できる点です。i,j,kを全て管理するなら、構造体などが必要です。
提出コード
F - typewriter
包除原理で解けます。
包除原理についてはネットで調べると出てくるので、詳細は割愛します。
ここでは、小さい例で示します。
を使用する文字の集合のみから得られる長さの文字列の数とします。
を使用する文字の集合から得られる全ての長さの文字列の数とします。
入力例1でいえば、
は、実は簡単に求められます。に含まれる文字は何回使っても良いので、番目の文字は通りから選ぶことになり、選ぶ文字によって得られる文字列は異なるので、
となります。これは繰り返し二乗法により、で計算できます。
(とはいえ、の求め方がコンテスト中悩んでしまいました。重複組み合わせと重複並び替えを組み合わせようとしてしまっていました。)
では、のケースで、包除原理の一例を示します。
今求めたいものは、
です。ベン図で他の集合と重なっていない部分の和と考えればわかりやすいです。
初め、とします。
まず、を足したいです。
より、にを足します。すると、
次に、は一個ずつ多いので減らしたいです。
より、からを引きます。
最後に、が足りなくなったので、足したいです。
より、にを足します。
この時点で、となります。
結局、
となります。
これを一般化すると、の積集合に含まれるの個数が奇数なら足す、偶数なら引くということになります。
解法に移ります。
とします。
における使用可能なアルファベットの集合はビットによる冪集合で管理すればよいです。
の全部分集合もビット全探索すればよいです。
に含まれるの数や、に含まれるアルファベットの数はpopcountで得られます。
計算量は、部分集合の全探索に、全探索中のそれぞれについて積集合を求めるのに、積集合から得られる文字列の数を求めるのになので、全体で多分です。
提出コード
ABC245 参加記録
コンテスト中AC:A〜C
コンテスト後解説なしでE,FをAC。
Dは解説見たが、方針は間違っていなかったが、考慮できていない部分があった。
D - Polynomial division
であるので、
の時、
の時、
として、の順で求められると考えました。
(なお、の時はとしておけば良い)
しかしながら、これはREになっていました。
なぜかというと、の場合があるからでした。
そこで、以下のように考えました。
となる最小のをとします。
となるので、以下の方針としました。
の時、
の時、
とします。 しかし、この解法でもREとなり、時間内にACできませんでした。
コンテスト後に考えた結果、実装時に色々と考慮できていなかった部分があったことが原因であり、解法自体は正しいことがわかりました。
以下がACしたコードです。
上の実装に、として、について、それぞれ個分の領域を確保する処理がありますが、ここは不要です。
全て個分確保しておけば良いです。逆にこれより少なくとだめです。
なぜなら、のみが保証されているため、は最大でになるからです。
また、今回、のインデックスをベースに実装しましたが、をベースにした方がわかりやすいと思います。
提出コード
E - Wrapping Chocolate
小さいチョコレートから優先して箱に入れる場合、大きい箱に入れると、後々入れられないチョコレートが出てくる可能性があります。そこで、チョコレートを大きいものから順に入れることを考えます。
ここでチョコレートの大小関係を定義しておきます。チョコレートを縦を第一キー、横を第二キーとして降順ソートしたときの基準を適用します。すなわち、番目の番目のチョコレートについて、
- の場合は番目が大きく、の場合はなら番目が大きい
となります。
まだ箱に入れられていないチョコレートの中で最大のものを番目とします。
まず、縦を考えます。
を満たす箱に入れることが可能です。ここで、これを満たす箱であれば、どれに入れても、縦の制限によって箱に入れられないチョコレートが発生することはありません。なぜなら、残ったチョコレートの縦は以下だからです。
次に横を考えます。
横については、を満たす箱ならどれでもよいというわけにはいきません。なぜなら、残ったチョコレートの横は以下であるとは限らないからです。
ということから、横はを満たす中で最小のものにしておく必要があります。
以上をまとめると、以下のような貪欲法になります。
1.箱に入っていないチョコレートの中で最大をものを1つ選びチョコレートとする
2.チョコレートが入っておらず、かつ、チョコレートが入れられる箱の中で、横が最小の箱を選ぶ
3.チョコレートを箱に入れる
具体的はアルゴリズムに移ります。
順序付き多重集合にチョコレートを入れる候補となる箱を記録することにします。の順序はの昇順とします。
には、チョコレートを入れたい時、を満たす箱を全て記録します。その中から、を満たす中で最小の箱を二分探索によって探し、から削除します。
なお、に入っている箱は全てを満たすので、は記録する必要がなく、だけ記録しておけばよいです。
これを素直に実装すると、各チョコレートについて、候補となる箱を探すため、集合への追加を除いてもとなり、間に合いません。
そこで以下の方法とします。
まず、チョコレートと箱はそれぞれ縦()を第一キーに、横()を第二キーとして降順ソートします。
の時、なので、である時、となります。
そのため、チョコレートでに追加した箱は、チョコレートでも追加したままにしておけます。
従って、の順に調べる時、 チョコレートでに追加された箱は、チョコレートでも追加したままにでき、チョコレートで新たに必要になった箱だけをに追加すればよくなります。
これにより、箱はに高々1回しか追加されず、全体でとなります。
多重集合としてC++のmultisetを使えば、追加、検索、削除はlog時間でできるので、高速に実現できます。
F - Endless Walk
頂点を出発して、頂点に帰って来れるような閉路が存在すれば、に到達後に無限に移動できることが確定します。
ある強連結成分に属する全ての頂点は、この条件を満たします。
よって、強連結成分の集合族をとしたとき、これら強連結成分に属するいずれかの頂点に到達できれば、無限に移動できます。
強連結成分は、強連結成分分解でで得られます。AtCoder Libraryのscc_graphを強連結成分が二次元配列で得られるので、列のサイズが2以上であれば閉路を含み、とみなせます。
(補足:グラフが単純でなければ、自己ループを考慮する必要があるので、サイズが1でも閉路となります。)
あとは、強連結成分の頂点に到達可能な頂点を調べれば良いですが、以下のように言い換えました。
- の有向辺を全て逆向きにしたグラフにおいて、強連結成分のいずれかの頂点から到達可能な頂点を調べる
これは強連結成分に属する全ての頂点をキューに入れてBFSを開始すれば良いです。
ABC244 参加記録
コンテスト中AC:A〜E
C - Yamanote Line Game
D - Swap Hats
自信ないまま通してしまいました。
操作を偶数回行った後に生成される状態には回行った後もその状態にできます。
10回操作すると状態数はになるものの、RGBの順番の入れ替えの通り数は6通りしかありません。
そのため、を10回操作してにできれば達成できると考えました。
実際に実装してデバッグしてみると、状態数は偶数回、奇数回共に早々に3個に限定され、解を得られそうだとわかりました。
そして、提出した結果ACできました。
公式解説によれば、6種類の状態の関係性をグラフにすると、二部グラフになり、が同一のグループになれば達成できるということでした。
E - King Bombee
パスに関する以下のDPを考えます。
回移動した後に頂点にいるようなパスの総数
初期値は
これは、のうち、であるものの総数に一致します。
このDPテーブルの更新は、以下のような3重ループになります。
for k = 1からKまで for u in 全ての頂点 for v in uと接続する全ての頂点 dp[v][k] = dp[u][k-1] //uからvに移動する
for v in uと接続する全ての頂点
の部分は、のループの1回につき、合計で回しか実行されないので、になります。
また、回目でにいるパスの総数はとなります。
ここまでは、同様の考え方が過去に出題されました。
あとはに訪れた回数(に含まれるの個数)の偶奇の情報が欲しいので、DPテーブルに加えます。
回移動した後に頂点にいて、かつに訪れた回数の偶奇がであるようなパスの総数
但し、であり、の時はに訪れた回数が偶数、の時は奇数
DPテーブルの更新は、以下のようにの時だけ、偶奇を入れ替えればよいです。
for k = 1からKまで for u in 全ての頂点 for v in uと接続する全ての頂点 if vがX dp[v][k][0] = dp[u][k-1][1] dp[v][k][1] = dp[u][k-1][0] else dp[v][k][0] = dp[u][k-1][0] dp[v][k][1] = dp[u][k-1][1]
答えはです。
提出コード
ARC137 参加記録
コンテスト中AC:なし
A,B解説AC
A - Coprime Pair
素数の間隔(prime gap)が重要とのことです。
公式解説及び上記のwikiによれば、問題の制約における素数間の極大の間隔(maximum gap)は1500らしいです。
よって、及びを満たす組のうち少なくとも1つはがいずれも素数である組が存在するということでした。
とすると、の範囲だけとなる組を調べれば良くなり、全探索可能になるということでした。
B - Count 1's
- コンテスト中にたどり着いた部分
連続した部分列の0の個数を、1の個数を、初期状態における1の個数をとすると、操作後の1の数をにできます。
また、の最大値をとし、その時の連続した部分列の区間を閉区間とします。
区間が空の状態から、+1ずつ伸長してにした時、も+1ずつしかされないので、にできます。
最小値についても同様です。
従って、は、を満たす全ての整数にすることができます。
以上から、に0を含むならが答えとなります。
0を含まないなら、空の部分列を選ぶと0にもできるので、となります。
- コンテスト中わからなかった部分
の求め方がわかりませんでした。
公式解説によれば簡単に求められるとなっていました。
閉区間の部分列に含まれる0の個数を、1の個数をとします。
に含まれる0の個数は、1の個数はです。
よって、のは以下のように計算できます。
従って、を末尾とする連続した部分列のが最大となるのは、を満たすのうち、が最小となるもの選んだ場合です(便宜上、 )。
同様に、が最小となるのは、が最大となるもの選んだ場合です。
よって、について、を順次計算し、最大値と最小値を管理しておくことで、で各を末尾とした時の連続した部分列のの最大・最小を求められます。
以上から、をで求められます。
ABC242G Range Pairing Query
解説AC
初Mo's Algotithm
Mo's Algorithmの適用可能条件は以下の通りです。
- 配列が不変
- クエリ先読み可
- 区間を+1, -1ずつ伸縮しても値の再計算が容易
1,2は満たすので3が満たせればよく、以下の方法で容易に計算できることがわかります。
:区間内のの出現数
とします。最大のペア数は、以下のように計算できます。
よって、Mo's Algorithmによりで解けます。
Mo's Algorithmは公式解説のリンクなど見れば良いですが、自分でも別記事に簡単にまとめました。
Mo's Algorithm
Mo's Algorithm
AtCoder Biginner Contestに出題されたので、実装してみました。
解説へのリンク(ネタバレ注意)
解説に書かれている以下のリンクに詳細があるので、アルゴリズムについては要約して書きます。
概要
数列についての個の閉区間のクエリにで答える。
やっていることはクエリの平方分割。
条件
- は不変
- クエリ先読み可
- から区間を伸縮しても、値を簡単に計算できる
アルゴリズム
- ブロック番号の昇順、同ブロック内ではの昇順でクエリをソートする
- ソート済みのクエリを左から順に区間を伸縮して値を計算していく
計算量
- 左端はほとんどがブロック内での移動なので、回
- 右端は同ブロック内で高々回の移動なので、回
- 上記を併せて
例
のサイズを9とします。
ブロックサイズ
与えられるクエリ
[5,7]
[1,3]
[3,5]
[4,4]
[7,8]
[2,5]
[0,8]
[7,7]
まず、クエリをソートします。
が属するブロックを第一キーに、を第二キーとして昇順にソートします。
が属するブロックはにより求められます。
ソート後(ブロック毎に区切ってあります。)
[1,3]
[2,5]
[0,8]
[4,4]
[3,5]
[5,7]
[7,7]
[7,8]
ソート後に順にクエリを処理します。
例えば、予め[2,5]の値は計算されていると仮定して[2,5] -> [0,8]を計算するときは、
- 右端を、[2,5] -> [2,6] -> [2,7] -> [2,8]という風に、+1ずつ区間を右に伸長しながら値を更新する
- 左端を、[2,5] -> [1,5] -> [0,5]という風に、-1ずつ区間を左に伸長しながら値を更新する
という感じです。
ABC243 参加記録
コンテスト中AC:A〜D
D - Moves on Binary Tree
公式解説の解法1とほぼ同じでした。
まず、根からへの移動方法の文字列を得ます。具体的には、
が1となるまで以下を行う。
の末尾に、が偶数なら
'L'
を、奇数なら'R'
を追加。
と更新。上記が終了したら、を反転させる。
その後、について、以下を行います。
が
'U'
なら、の末尾から文字を一つ取り除く。
そうでないなら、をの末尾に追加。
これによって、は根から答えのノードまでの移動方法の文字列となります。あとはこれに従って、根(番号1)から計算していけば良いです。
計算量は、までの文字列の大きさがで、最終的なの大きさもを超えないことから、だと思います。
書いていて気づきましたが、最終的には'U'
を含まないので、答えの番号を求めるときの分岐は不要でした。
解法2の考え方も思いついたのですが、ビットシフトしなければいけないと思い断念しました。
単純に末尾に末尾への追加・削除だけで良いことを思いつけなかったです。