コンテスト中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で得られます。
計算量は、部分集合の全探索に、全探索中のそれぞれについて積集合を求めるのに
、積集合から得られる文字列の数を求めるのに
なので、全体で多分
です。
提出コード