問題概要
種類のはしごがあり、
番目のはしごの長さは
です。
以上
以下の値
で、以下の条件を満たさないものの個数を求めてください。
種類のはしごを上手く使い、長さの総和を
にすることができる(ただし、同じはしごは何回でも使用可能、1度も使わなくても可)
個のテストケースが与えられるので、全てに答えてください。
Med:
Large:
Largeの制約では、最悪でも1分程度で答えが出る想定で、
Python、Javaでは1分以内に全ての答えが出力されることを確認しています。
解法
どの解法でも、1つ1つのテストケースに対して計算し、回繰り返すことを考えます。
Med
合計距離が
となるような組み合わせができるか(bool値)
として、
初期値:
遷移:
という計算をすると、で計算可能です。
個数制限無しナップサックを用いたもの。
Large
上記の解法だとメモリ、時間ともに足りません。
について全て調べたいですが、それぞれについて考えると、状態数が多くメモリと時間が足りなくなってしまうので、うまく状態数を減らしていくことを考えます(一般に、bool値のDPはより多くの情報を持たせて遷移させることができるはずです、例えば上記のbool値はできるかどうか、だけでなく、ほぼ同じ遷移で使用するはしごの最小個数をそのまま持たせることができます)。
とします。
距離のはしごのみを適切な回数用いることで、
の倍数については必ず達成できることは明らかです。
の倍数ではないときも、ある距離
が達成できた場合に、その後に距離
のはしごを繰り返し使うことで、
以上の、 距離を
で割った余りが
となる地点は全て達成できます。
なので、距離を
で割った余りそれぞれについて、可能な最小値を探せば、その最小値未満の値ができない、ということになります。ということで、
が
になるような合計距離
の中で、作成可能な最小値
を考えていきます。
で初期化をします。
より小さい値の個数をそれぞれの
について計算してあげると、その総和が答えです。ここは
です。
1つめ:01ナップサックに帰着
先ほどのdpについて、個数制限のないナップサック問題を適用することについて考えてみます。
という遷移になります。
,dp配列が
であったときを考えます。
普通、個数制限無しナップサックを考える場合は、dpの添え字0から順番に増やしていきますが、例えばだった場合、
次の遷移では
になります。しかし、添え字5から始めると、
になります。
(です)。
このように、始まる添え字によって値が変わり、うまくDPを行うことができません。
このことは、個数制限なし、という条件を、
距離のはしごの1個セット、2個セット、4個セット....を用意してあげて、
距離のはしご
個セットを使う/使わない
という2択にすることで、2進数で任意の個数を表現しつつ、うまく処理することができます。
個目のはしごセットについて使用する/しないを決めたとき、
が
になるような合計距離
の中で、作成可能な最小値
とすると、j個目のはしごセットの距離の総和をとして、
と遷移することができます。
はしごセットは、それぞれのはしごについて、となる
について考えればよいので、
種類で抑えられます(実は、
で抑えることもできます) 。
よって、で解けました。
2つめ:個数制限なしナップサック
1つめの解法の、個数制限なしナップサックに戻ってみます。
先ほど上手くいかなかったのは、添え字から、順番に
ずつ増やしていたからです。
ここを賢く走査することで、個数制限なしナップサックでも解けるようにしてしまいます。
種類目のはしごについて、dpの添え字の遷移は、
本のループにわかれます。
例えば、,
のとき、
というループと、
というループにわかれます。
1ずつ添え字を増やして見ていくのではなく、このそれぞれのループについて、個数制限なしナップサックを考えます。
このそれぞれのループについて、現在の最小値を探します。すると、その最小値の部分は、他がどうであれ、今見ている長さのはしごをどれだけ使っても、この最小値が更新されることはありません。
なので、この最小値のインデックスからスタートして、
と見ていけば、ループを1周するだけでうまく更新できます。
最小値を探すのと、更新それぞれで1周ずつすればよく、それぞれのループにある添え字の個数の合計は結局になるため、はしご一種類あたり
で計算できます(最小値を探さなくとも、適当な場所から更新をはじめ、2周する、という方法でもいいです)。
これを回繰り返すため、
でこの問題が解けました。
3つめ:ダイクストラ法
が
になるような合計距離
の中で、作成可能な最小値
に戻ります。
現在の距離がの際に、はしご
を新たに用いたとすると、
距離は
に、
は
になります。
について、添え字
を頂点番号と考え、頂点
ではしご
を使う操作を、コスト
、遷移先の頂点が
の辺としたグラフを考えることができ、
それぞれの頂点に行く、最短距離を求める問題としてみることができます。
最短距離といえばダイクストラ法です。
から、順にはしご
を使う操作による遷移を行い、
を埋めることをダイクストラ法を用いて行うと、
頂点数が、辺の本数が
本なので、
で解くことができます。
おまけ(Med解以外の想定TLE,MLE解法)
実は、距離が十分に大きい時、
~
すべてについての最大公約数を
とすると、
の倍数は全て構築可能、そうでない場合は構築不可能になります。
距離が十分に大きい、というのは、以上であればOKです。
つまり、までは愚直にMed解法と同じDPで見て、それより大きい部分については、
の倍数でない数を数えるだけで、この問題に対して答えることができます。
です。
コード
解法1,2についてはこちらにアップしました(2020/05/19)。
main.cが解法1つめ、mainver2.cが解法2つめです。