Waseda Orientation Programming Contest 2020 J - トレジャーハンター
問題概要
種類のはしごがあり、番目のはしごの長さはです。
以上以下の値で、以下の条件を満たさないものの個数を求めてください。
- 種類のはしごを上手く使い、長さの総和をにすることができる(ただし、同じはしごは何回でも使用可能、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つめです。