昨日の続き。
http://www.kmonos.net/wlog/53.php#_1604050908 の、トーナメントひいき問題。
紙とペンで考えてもわからないので、とりあえずプログラムを書いてみた。
まずは、総当たり方式(←バカ)。
ここで haskell で書くのが筋のように思えるが、やっぱり ruby。
require 'set' L = { 'A' => [ '', 'o', 'x', 'x', 'o', 'x', 'o', 'x' ], 'B' => [ 'x', '', 'o', 'x', 'x', 'o', 'x', 'x' ], 'C' => [ 'o', 'x', '', 'o', 'o', 'o', 'o', 'x' ], 'D' => [ 'o', 'o', 'x', '', 'o', 'o', 'o', 'o' ], 'E' => [ 'x', 'o', 'x', 'x', '', 'x', 'x', 'x' ], 'F' => [ 'o', 'x', 'x', 'x', 'o', '', 'x', 'x' ], 'G' => [ 'x', 'o', 'x', 'x', 'o', 'o', '', 'o' ], 'H' => [ 'o', 'o', 'o', 'x', 'o', 'o', 'x', '' ], } PLAYERS = L.keys table=Hash.new{ Set.new } def each_combi( a, n ) return if a.size<n || n<0 if a.size==n yield a else tail = a[1..-1] each_combi( tail, n-1 ){ |x| yield( [a[0]]+x ) } each_combi( tail, n ){ |x| yield( x ) } end end each_combi(PLAYERS, 2 ){ |x,y| xwin=L[x][ y[0]-?A ]=='o' ywin=L[y][ x[0]-?A ]=='o' if xwin==ywin raise "which will win? : #{x},#{y}" elsif xwin table[ x ] += y elsif ywin table[ y ] += x else raise "unexpected." end } Table = table def target_lost( c, target ) return ( if c.flatten.include? target target != find_winner( c ) else false end ) end def each_tment( players, target ) if players.size==1 then yield players else tail = players[ 1..-1 ] each_combi( tail, players.size/2 ){ |c1| c0 = players-c1 each_tment(c0, target){ |x0| next if target_lost( x0, target ) each_tment(c1, target){ |x1| next if target_lost( x1, target ) yield [x0,x1] } } } end end def find_winner( x ) case x when Array case x.size when 1 find_winner( x[0] ) else w = x.map{ |i| find_winner( i ) } (0..1).each{ |m| return w[m] if Table[w[m]].member?( w[1-m] ) } raise "#{w.join(',')} : not found in Table" end else x end end each_tment(PLAYERS, 'A'){ |x| winner = find_winner( x ) if winner=='A' puts( '%s => %s' % [ x.inspect, winner ] ) exit end }
総当たりと書いたが、「target_lost」を呼んでいるところで枝刈りをしている。
8人ぐらいでは枝刈りをしなくても一緒か、かえって遅いくらいなんだが、16人になると、計算が終わるか否かの決定的な要因になる。
乱数で問題を作ってみると、解がある場合については、16人でもだいたい10秒以下で計算が終わる。
解がない場合は、10分待っても終わらないのでそのまま寝たら、朝には終わっていた。時間は計ってない。
まだ実装していないが、枝刈りのできる既知のポイントがもう一つある。
それは。残り試合が n のとき、ターゲットとの距離(ターゲット自身は距離ゼロ。ターゲットに負ける人は距離1。距離n未満でなく、距離n-1の人に負ける人は距離n、と、定義する)が n+1 以上の人がいたら調査しない、というものだ。
これで、全勝の人がいたりした場合に諦めることができるようになる。
いや、それでもまだ足りない。
16人プレイヤーがいるときに、ターゲットが3人にしか勝てないケースが刈れていない。
と、枝刈りをあと2種類ぐらい実装すれば、いい線まで行けるんじゃないかと思っているんだが、数学を背景にしたきれいな実装ではないので、計算量を見積もるのは困難である。
それと。
id:Cryolite:20050908:p3 も、自分で実装してみるつもり。枝刈りも入れて。まだよく読んでないが。
#pruning って何? と思って辞書をあたると、枝刈りだった。あう。