パソコン甲子園 ケーキ屋
TRANSCRIPT
![Page 1: パソコン甲子園 ケーキ屋](https://reader031.vdocuments.pub/reader031/viewer/2022030300/588131081a28ab00438b6863/html5/thumbnails/1.jpg)
目次
問題の意図を読み取り、方針を立てる 変則的入力に対する対処 探索を用いた解法の説明
└探索の効率化 適用するアルゴリズム(最良優先探索)
ビット集合で状態を表す その他 おわりに
![Page 2: パソコン甲子園 ケーキ屋](https://reader031.vdocuments.pub/reader031/viewer/2022030300/588131081a28ab00438b6863/html5/thumbnails/2.jpg)
問題文の概要 PCK2005 本選問題問題 28 ケーキ屋(45点) [AOJ 0120]
・箱の大きさと、いくつかのロールケーキの半径が与えられる。
・ケーキを箱の底面に接するように並べた時(図a)、全て箱の中に収めることが出来ればOK。 そうでなければ”NA”を返しなさい。 ただし図bのようなことが起こらないような半径が与えられる。
・ロールケーキの入力個数の上限は12。
・半径の入力範囲は3〜10と狭い。
図a図b
![Page 3: パソコン甲子園 ケーキ屋](https://reader031.vdocuments.pub/reader031/viewer/2022030300/588131081a28ab00438b6863/html5/thumbnails/3.jpg)
rir i−1・
x0,r0=r0,r0
∣r i−r i−1∣
・length x1,r1
y
x
①問題をより詳しく分析
①底面に接するように配置する⇒どのケーキも中心 y座標=半径r②また、最初に置くロールケーキの中心x座標 = 半径rしかし、1コ目にしか②のルールは適用できない。どうすればいいか。⇒できるだけ全体の幅が小さくなる置き方とはどういうものかを考える。
円と円に隙間が無い置き方をする。⇔(互いの円の中心座標間の距離)=(互いの半径の和)になる置き方そして、これら条件を元に左から順に円の最適座標を求めていき、
(最後の円のx座標)+(最後の円の半径)≦箱の幅ならば敷き詰め成功となる。
![Page 4: パソコン甲子園 ケーキ屋](https://reader031.vdocuments.pub/reader031/viewer/2022030300/588131081a28ab00438b6863/html5/thumbnails/4.jpg)
length=4⋅r i⋅r i−1
∴ xi=xi−14⋅r i⋅r i−1
図より、 xi=xi−1length
判明しているx座標 < 求めたいx座標なので負の解は不適。
length2=r iri−1
2−r i−ri−1
2 =4⋅r i⋅r i−1
length=±4⋅r i⋅r i−1
length2r i−ri−12=r iri−1
2
三平方の定理 a2b2=c2[ca,b ] より、
また、問題の性質上、全てのy iについて、y i= r i
{x i=i==0⇒r0
i0⇒x i−14⋅r i⋅ri−1 右の証明より
nコのケーキを左から配置する際、一番左のケーキを0番目として、次のケーキを1番目...n−1番目と定義する。そして、
i番目のケーキの半径をr i, x座標をxi, y座標をy iと定義する。
rir i−1・
x0,r0=r0,r0
∣r i−r i−1∣
・
・
length x1,r1
不適⋯
x2,r2
y
x
②問題をより詳しく分析
![Page 5: パソコン甲子園 ケーキ屋](https://reader031.vdocuments.pub/reader031/viewer/2022030300/588131081a28ab00438b6863/html5/thumbnails/5.jpg)
変則的入力への対処
そもそもこの問題は入力にクセがある。各データセットは、
(箱の長さ(空白)1~n 個目のロールケーキの半径(整数;空白区切り)
という形式で1行で与えられる。
(Ex)50 3 3 3 10 10 (箱の長さが50で、5つのケーキの半径rが各3,3,3,10,10)
事前に入力される個数が与えられないというところがなかなか面倒くさい。
→ → パースしよう どうやって? → C++標準ライブラリのstringstreamを使う。
stringstreamは、ファイルのリダイレクトでプログラムに入力データを渡す時のように、文字列からそのようなふるまいをしてくれる。[sscanf()関数に似ている]スペース・改行は勝手に認識されデータの区切りに使われる。
例:stringstream ss(“123 4 56”);int test;ss >> test; (ssの中身:”4 56”, testの中身=123)ss >> test; (ssの中身:”56”, testの中身=4)ss >> test; (ssの中身:””, testの中身=56)
![Page 6: パソコン甲子園 ケーキ屋](https://reader031.vdocuments.pub/reader031/viewer/2022030300/588131081a28ab00438b6863/html5/thumbnails/6.jpg)
探索を用いた解法
この問題は、いくつかのアプローチで解くことができるが、今回は探索による解法を説明する。基本的には・全ての順列パターンを調べ、一つでも解となりうるものがあればOKとする。という全探索の方針だが、愚直にこれを実装すると、入力の最大個数が12なので、12コの順列の総数=12! = 479,001,600(約5億)通りあり、とてもではないが制限時間(1s)内には終わらないと予想できる。
そこで、枝狩りをする。枝狩りとは、明らかに無駄だと探索している途中に気づいたらそこで探索を中断するという手法。思いつきそうなものの例を挙げるなら・・・
①解が出た時点で全ての探索を打ち止め”OK”を出力する。②探索途中に箱の幅を超えてしまった場合にはその探索をやめ、別の探索に移る。
等が考えられる。
しかし、これらの枝狩りでは不十分である。①は解が無い場合には無意味となる。②はどう辿っても最後の一つ手前まではいけるがその最後の1つでギリギリ箱の幅をオーバーするなどのテストケースでは、結局12!に近い数の遷移をしてしまい、ほぼ無意味となる。
ならばどうすれば良いのか?
![Page 7: パソコン甲子園 ケーキ屋](https://reader031.vdocuments.pub/reader031/viewer/2022030300/588131081a28ab00438b6863/html5/thumbnails/7.jpg)
―①探索の効率化
そもそも状態数が多すぎる。 → どうやって減らす?
● 次に置くロールケーキには、最後に置いた(一番右)のケーキの情報にしか依存しないことに着目する。
この性質を利用して状態数を減らす!
y
x・
・
・
・
最初の円以外のどの円の座標も、配置する時は直前の円の座標にしか依存していない
![Page 8: パソコン甲子園 ケーキ屋](https://reader031.vdocuments.pub/reader031/viewer/2022030300/588131081a28ab00438b6863/html5/thumbnails/8.jpg)
―②探索の効率化
ケーキ①,②,③,④を使って現在一番右にあるのがケーキ④等の状態をまとめる。具体例で表すと、
これらのケースを一つの状態として考えるということ
①②④③ [注意!]これは別の状態です!(③が右端のため)
当然並び方によって合計幅は異なる。しかし、その状態にたどり着くまでの合計の幅を見て、枝狩りするか否か決定できる。例:ケーキ①,②,③,④を使ってケーキ④が右端の時の最小幅を記録する変数を作って、(下の1→2→3という順番にその状態にたどり着いたとする。)1.①②③④(合計幅10とする) 初めてこの状態を訪れた時はそれを最小として変数に記録する。2.③①②④(合計幅12とする) 現在のこの状態の最小合計幅は10なので、無駄だと判断し探索を打ち切る。
3.③②①④(合計幅 9とする) もし記録している最小合計幅(この時点だと10)未満のものがきたと したら、それを最小として変数に記録し、さらに探索を続ける。
即ち、変数より小さいのものならば更新し探索継続、それ以上のものが来たら枝狩り。
①②③④ ②③①④
①③②④ ③①②④
②①③④ ③②①④
![Page 9: パソコン甲子園 ケーキ屋](https://reader031.vdocuments.pub/reader031/viewer/2022030300/588131081a28ab00438b6863/html5/thumbnails/9.jpg)
―③探索の効率化 実際にどれくらい状態数が下がっているか[何を使っているのか][現在の右端のケーキは何番なのか]が状態。
「何を使っているのか」という情報は、(使っている) or (使っていない)の2通りが、入力の最大の12コあるので、2^12 = 4096通り
「現在の右端のケーキは何番なのか」というのは入力の最大が12コなのでそのまま12通りとなる。
∴ 4096 × 12 = 49,152通り
![Page 10: パソコン甲子園 ケーキ屋](https://reader031.vdocuments.pub/reader031/viewer/2022030300/588131081a28ab00438b6863/html5/thumbnails/10.jpg)
適用するアルゴリズム
状態とその最小幅をメモする変数があり、それに基づいてきちんと枝狩りできるなら、幅優先・深さ優先探索のどちらでも時間内に計算を終えることができるはず!
でももっと効率化したい・・・。⇒最良優先探索を使う。
最良優先探索とは?>幅優先探索を何らかの規則に従って次に探索する最も望ましいノードを選択するように拡張した探索アルゴリズムである。(Wikipediaより)
どういうことか?今回の問題に適用するとすれば、探索用キューにノードを入れた順序に関わらず、現在の幅がとにかく小さいものから遷移し、優先的に調べていくことで、結果としてどの状態にも最小の幅でたどりつくという性質のあるアルゴリズム。
これを使うことのメリットはどの状態にも最短のコストでたどり着ける⇔その状態に訪れたかだけが分かればよいである。この性質のために、そのため最小幅を記録する必要がない。∴ 最小幅を記録していたdouble型変数をbool型へ(64bit→1bit)
時間計算量も節約できるが、特に空間計算量を大きく節約できる。
![Page 11: パソコン甲子園 ケーキ屋](https://reader031.vdocuments.pub/reader031/viewer/2022030300/588131081a28ab00438b6863/html5/thumbnails/11.jpg)
ビット集合で状態を表す
ビットを利用し状態を表すというのは、今回のようにそれぞれのケーキの状態が2通り(使っているor使っていない)しかない場合に有効。
たとえば、各ケーキを表のビットで表現すると、
ケーキ1,ケーキ2を使っている状態は、(00011)2で表現できる。
ケーキ1,ケーキ2,ケーキ4を使っている状態は(01011)2と表現できる。
状態がただの整数型で管理できるので、効率的で実装が楽。
メモ化用変数は、bool memo[212][12] という形で宣言でき、扱える。
ケーキ1 ケーキ2 ケーキ3 ケーキ4 ........
2進数 00001 00010 00100 01000 .......
![Page 12: パソコン甲子園 ケーキ屋](https://reader031.vdocuments.pub/reader031/viewer/2022030300/588131081a28ab00438b6863/html5/thumbnails/12.jpg)
その他
・状態数について、もう少し減らすことが出来た。
「現在の先頭は何番目のケーキなのか」と考えていたところを「現在の先頭の半径はいくつなのか」と考える。(実際に僕がプログラムを実装する段階ではこっちを使った)
すると、 半径rの範囲は3~10なので、8通りとなり、4096 * 8 = 32,768とさらに少なくなった。
・最良優先探索のアルゴリズムにはダイクストラ法を用いた。
ダイクストラ法はグラフの最短経路探索等に利用されるが、『状態を頂点と見立て、その状態で次のケーキを置くのにかかる距離
を辺とのコスト見立てる』
という考え方をすることでこの問題にも適用することが可能になる。
![Page 13: パソコン甲子園 ケーキ屋](https://reader031.vdocuments.pub/reader031/viewer/2022030300/588131081a28ab00438b6863/html5/thumbnails/13.jpg)
おわりに
競技プログラミングは、アルゴリズムを学ぶのに最適だと思うので、ものづくりのためにプログラミングしている方にもおすすめしたい。
この問題は、探索的手法の他に、動的計画法(DP)と呼ばれる手法でも解ける。
上手く考えると貪欲法という非常に簡潔な手法でも解けるらしい! 考えてみてください。