Problem:
E: 鬼畜ババ抜き - Unbalanced Old Maid -

物語

高坂さんと園田さんと南さんは子供の頃からの仲良し3人組だ。3人は沖縄に修学旅行に行くも、台風が来てしまい、海で遊ぶことができないのでババ抜きをすることにした。

園田さんは勝負事には強いが、ポーカーフェイスが苦手なため、自分のカードが引かれるときに無意識にかわいらしい顔芸を披露してしまう。園田さんを愛して止まない高坂さんと南さんは、園田さんの顔芸を見るだけで園田さんの手札が分かってしまうため、園田さんのカードを引くときは、自分が有利になるように引くことができる。
一方、高坂さんと南さんは特に顔芸をしたりはしないので、手札がばれることはない。 よって、高坂さんと南さんのカードを引く人はそれぞれ、高坂さんと南さんが持っているカードの中から等確率で1枚引く。

どう考えても不利な園田さんは、ババ抜きでなかなか勝てないことに違和感を覚えている。 使用するカードの種類数nと初期の手札が与えられたとき、園田さんが負けとならない確率を求めてあげよう。

問題文

高坂さん、園田さん、南さんの3人は、以下の状況でババ抜きを行う。

使用するカードは、1からnまでの整数が書かれたn種類のカードそれぞれ4枚ずつとジョーカー1枚の合計4n+1枚である。

最初の手札が与えられたとき、3人はそれぞれ自分の手札に同一の整数が書かれたカードのペア（2枚組）があるならば、そのようなペアを全て捨てる。

3人は「高坂さんが南さんの手札からカードを引く」、「園田さんが高坂さんの手札からカードを引く」「南さんが園田さんの手札からカードを引く」という順番（ターン）で以下の操作を繰り返す。（このババ抜きでは、カードを引いた人が、次に引かれるというルールであることに注意せよ。）

一人がジョーカーのみを持ち、その人以外の2人の手札が空のとき、ババ抜きは終了し、ジョーカーを持っている人が負けとなる。

カードを引く順番の人の手札が空のとき、ターンを次の人とし、1.に戻る。

そうでなければ、カードを引く順番の人は決められた相手からカードを1枚引く。ただし、相手の手札が空のとき、まだ残っているもう1人からカードを引く。

引いたカードと同じ整数が書かれたカードがカードを引いた人の手札にあれば、その2枚を捨てる。

ターンを次の人とし、1.へ戻る。

ただし、園田さんのカードを引く人（南さんか高坂さん）は、以下の戦略でカード引く。

もし自分のカードと園田さんのカードで同じ整数が書かれたカードがあれば、それらのうち最小の整数が書かれたカードを引く

そうでないとき、園田さんがジョーカーでないカードを持っていれば、それらのうち最小の整数が書かれたカードを引く

そうでないなら、園田さんはジョーカーしか持っていないので、ジョーカーを引く

高坂さんと南さんのカードを引く人は、それぞれ高坂さんと南さんが持っているカードの中から等確率で1枚引く。

使用するカードの種類数nと3人の初期の手札が与えられたとき、園田さんが負けとならない確率を求めてあげよう。

入力形式

入力は4行からなり、以下の形式で与えられる。

n
m_1 c_{1,1} ... c_{1,m_1}
m_2 c_{2,1} ... c_{2,m_2}
m_3 c_{3,1} ... c_{3,m_3}

1行目にジョーカー以外のカードの種類数nが与えられる。
続く入力は3行からなり、2行目に高坂さんの手札、3行目に園田さんの手札、4行目に南さんの手札の情報が与えられる。
i+1 (1 ≤ i ≤ 3)行目では行頭に手持ちの枚数m_i、続けて手持ちのカードを表すm_i個の整数c_{i,j} (1 ≤ j ≤ m_i)が空白区切りで与えられる。 0はジョーカー、1からnはカードに書かれた整数を表す。

制約

1 ≤ n ≤ 100

m_1+m_2+m_3 = 4n+1

3人の手札で0はちょうど1つ、1からnはちょうど4回ずつ現れる

0 ≤ c_{i,j} ≤ n (1 ≤ i ≤ 3, 1 ≤ j ≤ m_i)

出力形式

園田さんが負けとならない確率を1行に出力せよ。答えには10^{−6}を超える絶対誤差があってはならない。

入力例1

1
1 1
3 0 1 1
1 1

出力例1

0.0

高坂さんが南さんのカード1を引き、高坂さんと南さんは手札が空になる。
園田さんはどうしても勝つことはできない。

入力例2

1
2 1 1
1 1
2 0 1

出力例2

0.5

はじめの高坂さんのターンは、高坂さんの手札が既に空なので何もしない。次の園田さんのターンでは、園田さんは南さんの手札のうちそれぞれ0.5の確率でカード1かジョーカーを引く。カード1を引いたとき、園田さんの手札は空となり勝利する。一方ジョーカーを引いたときは、次の南さんのターンで、南さんは確実にカード1を引くので園田さんが負けとなる。

入力例3

2
3 0 1 2
3 1 2 2
3 1 1 2

出力例3

0.5

入力例4

2
2 0 1
6 2 2 2 1 1 1
1 2

出力例4

0.6666666667

Solution in Scala (judge verdict: Accepted)



object Main extends App{

  import scala.io.StdIn.readLine
  val n = readLine().toInt
  val player1= readLine().split(' ').map(_.toInt).tail.groupBy(it => it)
  val player2 = readLine().split(' ').map(_.toInt).tail.groupBy(it => it)
  val player3 = readLine().split(' ').map(_.toInt).tail.groupBy(it => it)
  val kosaka = new PokerFacePlayer(player1.count{case (k, v) => v.length % 2 == 1}, player1.exists{case (k, v) => k == 0})
  val sonoda = new WeekPlayer(player2.count{case (k, v) => v.length % 2 == 1}, player2.exists{case (k, v) => k == 0})
  val minami = new PokerFacePlayer(player3.count{case (k, v) => v.length % 2 == 1}, player3.exists{case (k, v) => k == 0})
  val memo = Array.tabulate(101){_ => Array.tabulate(101){_ => Array.tabulate(101){_ => Array.tabulate(3){_ => -1d}}}}
  println(f(GameState(kosaka, sonoda, minami), memo))
  def f(gameState:GameState, memo:Array[Array[Array[Array[Double]]]]):Double = {
    if (gameState.IsSonodaWin) 1d
    else if (gameState.IsSonodaLose) 0d
    else {
      if (memo(gameState.Kosaka.CardNum)(gameState.Sonoda.CardNum)(gameState.Minami.CardNum)(gameState.PlayerHasJoker.toInt) < 0) {
        val next = gameState.Next().flatMap {
          case (gsSonoda, probSondoa) => gsSonoda.Next().flatMap {
            case (gsMinami, probMinami) => gsMinami.Next().map {
              case (gsKosaka, probKosaka) => (gsKosaka, probSondoa * probMinami * probKosaka)
            }
          }
        }.filter{case (g, p) => p != 0}
        val same = next.filter {
          case (gs, prob) => gs == gameState
        }.map { case (_, prob) => prob }.fold(0d) { case (a, b) => a + b }
        val diff = next.filter {
          case (gs, prob) => gs != gameState
        }.map { case (gs, prob) => f(gs, memo) * prob }.fold(0d) { case (a, b) => a + b }
        memo(gameState.Kosaka.CardNum)(gameState.Sonoda.CardNum)(gameState.Minami.CardNum)(gameState.PlayerHasJoker.toInt) = diff / (1 + same)
      }
      memo(gameState.Kosaka.CardNum)(gameState.Sonoda.CardNum)(gameState.Minami.CardNum)(gameState.PlayerHasJoker.toInt)
    }
  }
}
sealed trait Player{
  override def equals(obj: scala.Any): Boolean = obj match{
    case p:Player => CardNum == p.CardNum && HasJoker == p.HasJoker
    case _ => false
  }
  val CardNum:Int
  val HasJoker:Boolean
  def IsWin:Boolean = CardNum == 0
  def NumOfNumberCard:Int = CardNum - (if (HasJoker) 1 else 0)
  def Draw(drawer:Player, theOther:Player):List[(DrawType, Double)]
  def AddCard(drawType: DrawType):Player = {
    drawType match{
      case Joker => Copy(CardNum + 1, true)
      case MakePair => Copy(CardNum - 1, HasJoker)
      case NoPair => Copy(CardNum + 1, HasJoker)
      case _ => this
    }
  }
  def SubCard(drawType: DrawType):Player = {
    drawType match{
      case Joker => Copy(CardNum - 1, false)
      case NoCard => this
      case _ => Copy(CardNum - 1, HasJoker)
    }
  }
  protected def Copy(cardNum: Int, hasJoker:Boolean):Player
}
class PokerFacePlayer(val CardNum:Int, val HasJoker:Boolean) extends Player{
  override def Draw(drawer: Player, theOther: Player): List[(DrawType, Double)] = {
    val allType = (CardNum + drawer.CardNum + theOther.CardNum) / 2
    val canMakePairType:Double = allType - theOther.NumOfNumberCard
    val cantMakePairType = NumOfNumberCard - canMakePairType
    (MakePair, canMakePairType / CardNum)::(NoPair, cantMakePairType / CardNum)::(if (HasJoker) (Joker, 1d / CardNum)::Nil else Nil)
  }
  override protected def Copy(cardNum: Int, hasJoker: Boolean): Player = new PokerFacePlayer(cardNum, hasJoker)
}
class WeekPlayer(val CardNum:Int, val HasJoker:Boolean) extends Player {
  override def Draw(drawer: Player, theOther: Player): List[(DrawType, Double)] = {
    val allType = (CardNum + drawer.CardNum + theOther.CardNum) / 2
    val canMakePairType:Double = allType - theOther.NumOfNumberCard
    if (canMakePairType > 0) (MakePair, 1d)::Nil
    else if (NumOfNumberCard > 0) (NoPair, 1d)::Nil
    else (Joker, 1d)::Nil
  }
  override protected def Copy(cardNum: Int, hasJoker: Boolean): Player = new WeekPlayer(cardNum, hasJoker)
}
case class GameState(val Kosaka:Player, val Sonoda:Player, val Minami:Player, val Turn:PlayerName = PKosaka){
  assert(Turn != null)
  override def equals(obj: scala.Any): Boolean = obj match{
    case gs:GameState => Kosaka == gs.Kosaka && Sonoda == gs.Sonoda && Minami == gs.Minami && Turn == gs.Turn
    case _ => false
  }
  def PlayerHasJoker:PlayerName =
    if (Kosaka.HasJoker) PKosaka
    else if (Sonoda.HasJoker) PSonoda
    else PMinami
  def IsSonodaWin:Boolean = Sonoda.IsWin
  def IsSonodaLose:Boolean = Kosaka.IsWin && Minami.IsWin
  def Next():List[(GameState, Double)] = {
    if (IsSonodaWin || IsSonodaLose) (GameState(Kosaka, Sonoda, Minami, Turn.next), 1d)::Nil
    else {
      Turn match{
        case PKosaka =>
          if (Kosaka.IsWin) (GameState(Kosaka, Sonoda, Minami, Turn.next), 1d)::Nil
          else if (Minami.IsWin) Sonoda.Draw(Kosaka, Minami).map{case (t, d) => (GameState(Kosaka.AddCard(t), Sonoda.SubCard(t), Minami, Turn.next), d)}
          else Minami.Draw(Kosaka, Sonoda).map{case (t, d) => (GameState(Kosaka.AddCard(t), Sonoda, Minami.SubCard(t), Turn.next), d)}
        case PSonoda =>
          if (Sonoda.IsWin) (GameState(Kosaka, Sonoda, Minami, Turn.next), 1d)::Nil
          else if (Kosaka.IsWin) Minami.Draw(Sonoda, Kosaka).map{case (t, d) => (GameState(Kosaka, Sonoda.AddCard(t), Minami.SubCard(t), Turn.next), d)}
          else Kosaka.Draw(Sonoda, Minami).map{case (t, d) => (GameState(Kosaka.SubCard(t), Sonoda.AddCard(t), Minami, Turn.next), d)}
        case PMinami =>
          if (Minami.IsWin) (GameState(Kosaka, Sonoda, Minami, Turn.next), 1d)::Nil
          else if (Sonoda.IsWin) Kosaka.Draw(Minami, Sonoda).map{case (t, d) => (GameState(Kosaka.SubCard(t), Sonoda, Minami.AddCard(t), Turn.next), d)}
          else Sonoda.Draw(Minami, Kosaka).map{case (t, d) => (GameState(Kosaka, Sonoda.SubCard(t), Minami.AddCard(t), Turn.next), d)}
      }
    }
  }
  private def IsInvalid:Boolean = {
    val all = (Kosaka.NumOfNumberCard + Sonoda.NumOfNumberCard + Minami.NumOfNumberCard) / 2
    Kosaka.NumOfNumberCard > all || Sonoda.NumOfNumberCard > all || Minami.NumOfNumberCard > all

  }
}
sealed trait DrawType
object Joker extends DrawType
object MakePair extends DrawType
object NoPair extends DrawType
object NoCard extends DrawType
sealed trait PlayerName{
  val toInt:Int
  val next:PlayerName
}
object PKosaka extends PlayerName {
  override val toInt: Int = 0
  override val next: PlayerName = PSonoda
}
object PSonoda extends PlayerName {
  override val toInt: Int = 1
  override val next: PlayerName = PMinami
}
object PMinami extends PlayerName {
  override val toInt: Int = 2
  override val next: PlayerName = PKosaka
}