©AtCoder Inc. All rights reserved. 1
Chokudai Contest 001
解説
AtCoder株式会社 代表取締役
高橋 直大
©AtCoder Inc. All rights reserved. 2
はじめに
• Writer(chokudai)が、約8時間のテストプレイで試せ
た方針のみ明記しています。
• もっと良い方針(89万点以上)は、Twitterのハッシュ
タグ#chokudai_1から見つけることが可能かもしれま
せん。そちらも併せてご確認ください。
©AtCoder Inc. All rights reserved. 3
問題概要
• N×Nのマスに、1~100の整数が書かれている。
• マスをなぞると、マスの中に書かれている整数が1
減る
– なぞる順は、中の整数が8,7,6,5…のように、1ずつ減って
いるようにしなければならない
– なぞれるマスは、今見ているマスの上下左右のみ
• なぞる回数を出来るだけ小さくしなさい
• 今回の問題ではN = 30
©AtCoder Inc. All rights reserved. 4
まずは点数を取ろう!
• 点数を取るには?
– 連鎖とか考えずにとりあえず0にすることだけ考えよう!
– 各マスについて、マスに書かれている整数分だけ座標を
出力する。
• これで、1~100で30*30マスあるとして、最大90000手
• 10万-手数がスコアなので、これで最低でも1ケース1万点
• 実際は平均50程度なので、45000手程度となり、5.5万点
– 10ケースあるのでおおよそ55万点が得られる。
2016/3/20 4
©AtCoder Inc. All rights reserved. 5
ちょっと工夫をしよう!
• 例えば、横に1個だけ繋げてみる
– 右の数が自分より1だけ小さかったら、そっちも減らす
• これだけでもスコアが結構あがる!
– それが出来たら、再帰処理をして、連続して減らす
• これだけでも物凄く上がる!
– これをちゃんと組むだけでも70万点くらい行きます。
• ちゃんと書いてないから自信ない
2016/3/20 5
©AtCoder Inc. All rights reserved. 6
その他の工夫
• スコアを減らすマスを選んでみよう!
– 大きい数から減らしていった方が良さそう?
• 例えば、こんな貪欲法がある
– 盤面の中から一番大きい整数を選ぶ。
– そこから上下左右を見て、繋げるところがあれば出来るだけ連鎖を
させることを繰り返す。
– これも80万点弱取れる。
– たくさん減らせるマスを採用するべき?
• こんな貪欲法もある
– とりあえず全部のマスから出来るだけ繋げてみる
– 一番長いものを採用する
– これを最後まで繰り返す。
– これも80万点弱くらい?
2016/3/20 6
©AtCoder Inc. All rights reserved. 7
ランダムを使おう!
• ここから、ランダムで色々変えてみる
– 色々試すと当然スコアも変わる!
• 選択するマスを変えてみる
• ルートを変えてみる
– 制限時間いっぱいまで試して、一番良いスコアを選ぶ
– これでもスコアは上がるが、そんなに上がらない
• もうちょっと賢い方法を考えよう!
2016/3/20 7
©AtCoder Inc. All rights reserved. 8
ランダムを使おう!
• ビームサーチや焼きなまし
– ビームサーチ
• 途中までの手番のうち、K番目に良いものを保持する
– 「良いもの」というのは、自分で適当な評価関数を作る
» 減らせてる数とか
» 各マスの減らされ具合の分布とか
» 隣り合う数の近さとか
» 孤立してるかとか
» もちろん全部入れる必要はない
• そのまま最後まで進めると、「ある程度良いものの候補」を保持して探索が出来る
ので、単純なランダムより良い
• 焼きなまし
– 前の解をちょっと変えて試す!
– 今回は使い辛そう?
• この辺を頑張ると多分85万くらいまでは行きます。
– ここから先は、もうちょっと問題の性質を掴まないと難しい><
• 評価とか凄い頑張ると88万点台も出るっぽい・・・?
2016/3/20 8
©AtCoder Inc. All rights reserved. 9
補足
• ビームサーチや焼きなましにも限界が!
– 例えば、「54万点の出力の一部をランダムシャッフルした
後、シミュレートした答えを評価として焼きなましを行う」
• こんなんはまともなスコアが出ません
– ビームサーチや焼きなましを覚えることより、ちゃんと問
題の性質を掴むことの方が大切!
– 今回はこの辺りを使わなくても89万点は行けます。
2016/3/20 9
©AtCoder Inc. All rights reserved. 10
減らし方についての考察
• 一度消した列をひたすら減らし続けたりしそう。
– 例えば右図矢印のようにすると・・・?
• 赤い部分が孤立する!
• 孤立すると連鎖できなくなる!
– 逆に、横一線に減らすようにしてしまうと?
• 孤立点はなくなる
• 実は結構良い?
– でも適当なのでやっぱり大したことない
• どちらにしても「減らすパス」を意識しよう!
2016/3/20 10
©AtCoder Inc. All rights reserved. 11
「パス」を先に決める考え方
• 先に、「どういう順番で減らすか」を決めてしまうとど
うなるか?
– 例えば横一列が{7, 3, 6, 5, 3, 4, 1}だったとする。
– この1列を消すのに何手掛かるか?
• これは実は結構簡単に求まってしまう!
2016/3/20 11
©AtCoder Inc. All rights reserved. 12
「パス」を先に決める考え方
• 横一列が{7, 3, 6, 5, 3, 4, 1}だったとする。
– とりあえず下図みたいに書ける
– ここから、どう減らすのが最善かを考える
2016/3/20 12
1
2
3
4
5
6
7
1
2
3
1
2
3
4
5
6
1
2
3
4
5
1
2
3
1
2
3
4
1
©AtCoder Inc. All rights reserved. 13
「パス」を先に決める考え方
• まず、赤く塗った部分は、前から繋がる部分が存在
しないので、絶対に塗らないといけない
• ここから右下に伸ばすと、全てを埋め尽くせる
– つまり、赤い部分を数えるだけで、手数が解る!
2016/3/20 13
1
2
3
4
5
6
7
1
2
3
1
2
3
4
5
6
1
2
3
4
5
1
2
3
1
2
3
4
1
©AtCoder Inc. All rights reserved. 14
パスに対する手数の求め方
• 赤い部分の数え方は非常に簡単
– 各マスについて、max(0, 1つ前のマスとの差+1)が、赤い
部分の個数になる。
• 前のマスより1つ下がったところが最高到達点となるため。
– これを利用すると、マスの順序をパスと呼び、その個数を
Lとすると、
• ライン全体の手数を求めるのは、ラインに含まれるすべてのマス
の赤い部分を調べれば良いので、O(L)
• ラインに1つマスを追加するのは、新規追加マスの赤い部分を調
べれば良いので、O(1)
• ライン2つの結合も、結合部分だけ調べれば良いのでO(1)
– 非常に早く計算出来る!
2016/3/20 14
©AtCoder Inc. All rights reserved. 15
貪欲法との違い
• 例えば、{8,7,6,5,4,4,3,2,1}みたいなのがあるとする
– 貪欲法だと、大体こんな感じになる
• 8,7,6,5,4を減らす
• 7,6,5,4,3を減らす
• 6,5,4,3,2を減らす
• ・・・・
• {4,3,2,1,0,4,3,2,1}になり、ここから、2つの4,3,2,1を減らす
– 12手?
– 今回の方法だと、もっと賢くなる
• 4,3,2,1を減らす
• {8,7,6,5,4,3,2,1, 0}になり、これを減らす
– 9手?
– 貪欲法で生じていた無駄が、パスを決めることでなくなった!
2016/3/20 15
©AtCoder Inc. All rights reserved. 16
全体をラインで埋めよう!
• パスを作ることで効率よく処理できる?
– なら全体を1本のパスで埋めちゃおう!
• とりあえず雑にするとこんなん
– これで1本道になった!
– 実はこれでさっきの処理をすると84万点
• あとはこれを頑張って効率化しよう!
– 線の引き方を工夫してロスをなくしたり
– ランダムで色々ためしてみたり
2016/3/20 16
©AtCoder Inc. All rights reserved. 17
全体をパスで埋めないとだめ?
• さっきの例では全ての線を埋めたが、別に複数のパ
スがあっても問題ない
– 繋がっていない分ロスは生まれやすいが
• よって、実装が大変だったらこれでも良い
– これでも84万点近くに行きます。
2016/3/20 17
©AtCoder Inc. All rights reserved. 18
パスの作り方
• 隣り合う数の差が小さくなるようにパスを作ることで、
手数を減らすことが出来る
– つまり、パスの作り方で貪欲法を使う?
• これをすると、無駄な隙間が出来てしまい、あまり良
いスコアにならない
– 隙間が出来ないようにパスを作ろう
2016/3/20 18
©AtCoder Inc. All rights reserved. 19
パスの作り方 探索編
• 例えばこの状態の時、
– たくさん埋まっているマスの評価を上げる
• 3方向から埋められているマスは超評価を上げる(赤)
• 2方向から埋められているマスは評価を上げる(緑)
• 1方向から埋められているマスは普通の評価(青)
– 行き止まりに気を付ければ、良いパスが作りやすい!
• もうちょっと細かいところを気にした方がもちろん良い
– これを評価に加えて探索する
– 普通にやったら82万点くらい?
• まともなパスにするのはだいぶ難しい
– 凄く頑張ると88万点台?
• ビームサーチとかが使えます。
2016/3/20 19
©AtCoder Inc. All rights reserved. 20
パスの作り方 DP
• 探索は難しい!DPにしよう!
– dp[L][R][U][D][A][B]を使って状態を更新する
• 長方形(L, U)-(R, D)の区間が最大で、四隅に0,1,2,3と番号をつけ
た時に、パスの始点がA、終点がBな時の、最短手数を入れる
» 最短と言っても、あくまで「見つけた中での最短」
– 横に分割する時は、dp[L][X][U][D][A][i]と、
dp[X][R][U][D][j][B]を使って更新する。縦もやる。
• イメージはこんな感じ。切るx座標Xや、接続点iが複数ある。
2016/3/20 20
©AtCoder Inc. All rights reserved. 21
パスの作り方 DP
• このDPも更新の仕方を複数作れる
– 例えばこんな感じの分け方をすると、パスが2つ出来るが、
パスの始点・終点は維持される。
• もう一方は無視をするような形になる。
– こういう工夫を頑張って色々すると89万点に乗ります。
2016/3/20 21
©AtCoder Inc. All rights reserved. 22
パスの作り方 DP
• このDPも更新の仕方を複数作れる
– 例えばこんな感じの分け方をすると、パスが2つ出来るが、
パスの始点・終点は維持される。
• もう一方は無視をするような形になる。
– こういう工夫を頑張って色々すると89万点に乗ります。
2016/3/20 22
©AtCoder Inc. All rights reserved. 23
パスよりももっと工夫しよう!
• 実はこの問題、「なぞる方向を右か下だけに限定す
る」という制約をつければ、最適解が求まる!
– 入口と出口で二部グラフを作り、最大マッチング
• これだけで88.5万点出たりします。
2016/3/20 23
(1,1)
2
(2,1)
1
(1,2)
2
(2,2)
1
(1,1)
2
(1,1)
1
(2,1)
1
(1,2)
2
(1,2)
1
(2,2)
1
(1,1)
2
(1,1)
1
(2,1)
1
(1,2)
2
(1,2)
1
(2,2)
1
©AtCoder Inc. All rights reserved. 24
DAGの最適解が出ることを利用すると?
• なんでこれで解けるの?
– (1,1)の時点で10だったとして、(4,3)の時点では?
• 右に3回、下に2回移動しているので、絶対に5まで下がっている
• これは、移動方法に依存しない
– つまり、(A,B)時点でCの時、というパターンは、C-(A+B)が等しい
パターンしか通らない!
• 右下にしか行かない、という制約があるため
– よって、200枚程度のレイヤーに分かれた感じになるため、上レ
イヤーから順番に処理すれば、順番が前後するなどの事故が
起こらない。
• 中心位置を変えるなどをランダムで試したりすると89万
点弱まで出る!
– 今のところパス解法の方が強いですが、こっちも強くなるかも
2016/3/20 24

Chokudai Contest 001

  • 1.
    ©AtCoder Inc. Allrights reserved. 1 Chokudai Contest 001 解説 AtCoder株式会社 代表取締役 高橋 直大
  • 2.
    ©AtCoder Inc. Allrights reserved. 2 はじめに • Writer(chokudai)が、約8時間のテストプレイで試せ た方針のみ明記しています。 • もっと良い方針(89万点以上)は、Twitterのハッシュ タグ#chokudai_1から見つけることが可能かもしれま せん。そちらも併せてご確認ください。
  • 3.
    ©AtCoder Inc. Allrights reserved. 3 問題概要 • N×Nのマスに、1~100の整数が書かれている。 • マスをなぞると、マスの中に書かれている整数が1 減る – なぞる順は、中の整数が8,7,6,5…のように、1ずつ減って いるようにしなければならない – なぞれるマスは、今見ているマスの上下左右のみ • なぞる回数を出来るだけ小さくしなさい • 今回の問題ではN = 30
  • 4.
    ©AtCoder Inc. Allrights reserved. 4 まずは点数を取ろう! • 点数を取るには? – 連鎖とか考えずにとりあえず0にすることだけ考えよう! – 各マスについて、マスに書かれている整数分だけ座標を 出力する。 • これで、1~100で30*30マスあるとして、最大90000手 • 10万-手数がスコアなので、これで最低でも1ケース1万点 • 実際は平均50程度なので、45000手程度となり、5.5万点 – 10ケースあるのでおおよそ55万点が得られる。 2016/3/20 4
  • 5.
    ©AtCoder Inc. Allrights reserved. 5 ちょっと工夫をしよう! • 例えば、横に1個だけ繋げてみる – 右の数が自分より1だけ小さかったら、そっちも減らす • これだけでもスコアが結構あがる! – それが出来たら、再帰処理をして、連続して減らす • これだけでも物凄く上がる! – これをちゃんと組むだけでも70万点くらい行きます。 • ちゃんと書いてないから自信ない 2016/3/20 5
  • 6.
    ©AtCoder Inc. Allrights reserved. 6 その他の工夫 • スコアを減らすマスを選んでみよう! – 大きい数から減らしていった方が良さそう? • 例えば、こんな貪欲法がある – 盤面の中から一番大きい整数を選ぶ。 – そこから上下左右を見て、繋げるところがあれば出来るだけ連鎖を させることを繰り返す。 – これも80万点弱取れる。 – たくさん減らせるマスを採用するべき? • こんな貪欲法もある – とりあえず全部のマスから出来るだけ繋げてみる – 一番長いものを採用する – これを最後まで繰り返す。 – これも80万点弱くらい? 2016/3/20 6
  • 7.
    ©AtCoder Inc. Allrights reserved. 7 ランダムを使おう! • ここから、ランダムで色々変えてみる – 色々試すと当然スコアも変わる! • 選択するマスを変えてみる • ルートを変えてみる – 制限時間いっぱいまで試して、一番良いスコアを選ぶ – これでもスコアは上がるが、そんなに上がらない • もうちょっと賢い方法を考えよう! 2016/3/20 7
  • 8.
    ©AtCoder Inc. Allrights reserved. 8 ランダムを使おう! • ビームサーチや焼きなまし – ビームサーチ • 途中までの手番のうち、K番目に良いものを保持する – 「良いもの」というのは、自分で適当な評価関数を作る » 減らせてる数とか » 各マスの減らされ具合の分布とか » 隣り合う数の近さとか » 孤立してるかとか » もちろん全部入れる必要はない • そのまま最後まで進めると、「ある程度良いものの候補」を保持して探索が出来る ので、単純なランダムより良い • 焼きなまし – 前の解をちょっと変えて試す! – 今回は使い辛そう? • この辺を頑張ると多分85万くらいまでは行きます。 – ここから先は、もうちょっと問題の性質を掴まないと難しい>< • 評価とか凄い頑張ると88万点台も出るっぽい・・・? 2016/3/20 8
  • 9.
    ©AtCoder Inc. Allrights reserved. 9 補足 • ビームサーチや焼きなましにも限界が! – 例えば、「54万点の出力の一部をランダムシャッフルした 後、シミュレートした答えを評価として焼きなましを行う」 • こんなんはまともなスコアが出ません – ビームサーチや焼きなましを覚えることより、ちゃんと問 題の性質を掴むことの方が大切! – 今回はこの辺りを使わなくても89万点は行けます。 2016/3/20 9
  • 10.
    ©AtCoder Inc. Allrights reserved. 10 減らし方についての考察 • 一度消した列をひたすら減らし続けたりしそう。 – 例えば右図矢印のようにすると・・・? • 赤い部分が孤立する! • 孤立すると連鎖できなくなる! – 逆に、横一線に減らすようにしてしまうと? • 孤立点はなくなる • 実は結構良い? – でも適当なのでやっぱり大したことない • どちらにしても「減らすパス」を意識しよう! 2016/3/20 10
  • 11.
    ©AtCoder Inc. Allrights reserved. 11 「パス」を先に決める考え方 • 先に、「どういう順番で減らすか」を決めてしまうとど うなるか? – 例えば横一列が{7, 3, 6, 5, 3, 4, 1}だったとする。 – この1列を消すのに何手掛かるか? • これは実は結構簡単に求まってしまう! 2016/3/20 11
  • 12.
    ©AtCoder Inc. Allrights reserved. 12 「パス」を先に決める考え方 • 横一列が{7, 3, 6, 5, 3, 4, 1}だったとする。 – とりあえず下図みたいに書ける – ここから、どう減らすのが最善かを考える 2016/3/20 12 1 2 3 4 5 6 7 1 2 3 1 2 3 4 5 6 1 2 3 4 5 1 2 3 1 2 3 4 1
  • 13.
    ©AtCoder Inc. Allrights reserved. 13 「パス」を先に決める考え方 • まず、赤く塗った部分は、前から繋がる部分が存在 しないので、絶対に塗らないといけない • ここから右下に伸ばすと、全てを埋め尽くせる – つまり、赤い部分を数えるだけで、手数が解る! 2016/3/20 13 1 2 3 4 5 6 7 1 2 3 1 2 3 4 5 6 1 2 3 4 5 1 2 3 1 2 3 4 1
  • 14.
    ©AtCoder Inc. Allrights reserved. 14 パスに対する手数の求め方 • 赤い部分の数え方は非常に簡単 – 各マスについて、max(0, 1つ前のマスとの差+1)が、赤い 部分の個数になる。 • 前のマスより1つ下がったところが最高到達点となるため。 – これを利用すると、マスの順序をパスと呼び、その個数を Lとすると、 • ライン全体の手数を求めるのは、ラインに含まれるすべてのマス の赤い部分を調べれば良いので、O(L) • ラインに1つマスを追加するのは、新規追加マスの赤い部分を調 べれば良いので、O(1) • ライン2つの結合も、結合部分だけ調べれば良いのでO(1) – 非常に早く計算出来る! 2016/3/20 14
  • 15.
    ©AtCoder Inc. Allrights reserved. 15 貪欲法との違い • 例えば、{8,7,6,5,4,4,3,2,1}みたいなのがあるとする – 貪欲法だと、大体こんな感じになる • 8,7,6,5,4を減らす • 7,6,5,4,3を減らす • 6,5,4,3,2を減らす • ・・・・ • {4,3,2,1,0,4,3,2,1}になり、ここから、2つの4,3,2,1を減らす – 12手? – 今回の方法だと、もっと賢くなる • 4,3,2,1を減らす • {8,7,6,5,4,3,2,1, 0}になり、これを減らす – 9手? – 貪欲法で生じていた無駄が、パスを決めることでなくなった! 2016/3/20 15
  • 16.
    ©AtCoder Inc. Allrights reserved. 16 全体をラインで埋めよう! • パスを作ることで効率よく処理できる? – なら全体を1本のパスで埋めちゃおう! • とりあえず雑にするとこんなん – これで1本道になった! – 実はこれでさっきの処理をすると84万点 • あとはこれを頑張って効率化しよう! – 線の引き方を工夫してロスをなくしたり – ランダムで色々ためしてみたり 2016/3/20 16
  • 17.
    ©AtCoder Inc. Allrights reserved. 17 全体をパスで埋めないとだめ? • さっきの例では全ての線を埋めたが、別に複数のパ スがあっても問題ない – 繋がっていない分ロスは生まれやすいが • よって、実装が大変だったらこれでも良い – これでも84万点近くに行きます。 2016/3/20 17
  • 18.
    ©AtCoder Inc. Allrights reserved. 18 パスの作り方 • 隣り合う数の差が小さくなるようにパスを作ることで、 手数を減らすことが出来る – つまり、パスの作り方で貪欲法を使う? • これをすると、無駄な隙間が出来てしまい、あまり良 いスコアにならない – 隙間が出来ないようにパスを作ろう 2016/3/20 18
  • 19.
    ©AtCoder Inc. Allrights reserved. 19 パスの作り方 探索編 • 例えばこの状態の時、 – たくさん埋まっているマスの評価を上げる • 3方向から埋められているマスは超評価を上げる(赤) • 2方向から埋められているマスは評価を上げる(緑) • 1方向から埋められているマスは普通の評価(青) – 行き止まりに気を付ければ、良いパスが作りやすい! • もうちょっと細かいところを気にした方がもちろん良い – これを評価に加えて探索する – 普通にやったら82万点くらい? • まともなパスにするのはだいぶ難しい – 凄く頑張ると88万点台? • ビームサーチとかが使えます。 2016/3/20 19
  • 20.
    ©AtCoder Inc. Allrights reserved. 20 パスの作り方 DP • 探索は難しい!DPにしよう! – dp[L][R][U][D][A][B]を使って状態を更新する • 長方形(L, U)-(R, D)の区間が最大で、四隅に0,1,2,3と番号をつけ た時に、パスの始点がA、終点がBな時の、最短手数を入れる » 最短と言っても、あくまで「見つけた中での最短」 – 横に分割する時は、dp[L][X][U][D][A][i]と、 dp[X][R][U][D][j][B]を使って更新する。縦もやる。 • イメージはこんな感じ。切るx座標Xや、接続点iが複数ある。 2016/3/20 20
  • 21.
    ©AtCoder Inc. Allrights reserved. 21 パスの作り方 DP • このDPも更新の仕方を複数作れる – 例えばこんな感じの分け方をすると、パスが2つ出来るが、 パスの始点・終点は維持される。 • もう一方は無視をするような形になる。 – こういう工夫を頑張って色々すると89万点に乗ります。 2016/3/20 21
  • 22.
    ©AtCoder Inc. Allrights reserved. 22 パスの作り方 DP • このDPも更新の仕方を複数作れる – 例えばこんな感じの分け方をすると、パスが2つ出来るが、 パスの始点・終点は維持される。 • もう一方は無視をするような形になる。 – こういう工夫を頑張って色々すると89万点に乗ります。 2016/3/20 22
  • 23.
    ©AtCoder Inc. Allrights reserved. 23 パスよりももっと工夫しよう! • 実はこの問題、「なぞる方向を右か下だけに限定す る」という制約をつければ、最適解が求まる! – 入口と出口で二部グラフを作り、最大マッチング • これだけで88.5万点出たりします。 2016/3/20 23 (1,1) 2 (2,1) 1 (1,2) 2 (2,2) 1 (1,1) 2 (1,1) 1 (2,1) 1 (1,2) 2 (1,2) 1 (2,2) 1 (1,1) 2 (1,1) 1 (2,1) 1 (1,2) 2 (1,2) 1 (2,2) 1
  • 24.
    ©AtCoder Inc. Allrights reserved. 24 DAGの最適解が出ることを利用すると? • なんでこれで解けるの? – (1,1)の時点で10だったとして、(4,3)の時点では? • 右に3回、下に2回移動しているので、絶対に5まで下がっている • これは、移動方法に依存しない – つまり、(A,B)時点でCの時、というパターンは、C-(A+B)が等しい パターンしか通らない! • 右下にしか行かない、という制約があるため – よって、200枚程度のレイヤーに分かれた感じになるため、上レ イヤーから順番に処理すれば、順番が前後するなどの事故が 起こらない。 • 中心位置を変えるなどをランダムで試したりすると89万 点弱まで出る! – 今のところパス解法の方が強いですが、こっちも強くなるかも 2016/3/20 24