TopCoder SRM 384 Div1 Medium SchoolTrip

問題

1番からn番の生徒n人が輪になって次のようなゲームをする。

  • 最初は1番の人のターン。ターンは時計まわりに進む
  • ターンの人は、だれか一人を選んでボールを投げる。
  • それが命中する確率はprobability[i]%
  • 命中したら、当たった人は輪から抜ける

これを最後の一人になるまで繰り返す。


生徒たちはゲームを嫌々やっているので(本文中にそう書いてある。わろす)、
終了までにかかるターンの期待値が最小になるように、ボールを投げる相手を選ぶ。


終了までにかかるターンの期待値を求めよ。

制約条件

n≦60
10≦probability[i]≦100

方針

dp[i][pos][mask]を、iターン経過したときに、
pos番目の人のターンで、maskの人が残っているときにかかるターンの期待値とする。


dp[i][pos][mask]は、
jを1からnとして
dp[i+1][pos+1][mask^j]*p[pos]+dp[i+1][pos+1][mask]*(1-p[pos])+1
の最小値になるため、
これを更新していくようなDPができる。


これを収束するまで(1000回程度でよいらしい)まわす。
ボールを自分にぶつけるのを許してもサンプルが通ってしまう……

ソースコード

typdef long double ld;
const int MX=10000;
int n;
ld p[6],dp[2][6][1<<6];

class SchoolTrip {
  public:
  double duration(vector <int> probability) {
    n=probability.size();
    rep(i,n)p[i]=probability[i]/100.0;
    
    int cur=0,next=1;
    rep(j,6)rep(k,1<<6)dp[0][j][k]=0;
    
    rep(t,MX){
      rep(c,n)rep(mask,1<<n){
        if(__builtin_popcount(mask)<2){
          dp[next][c][mask]=dp[cur][c][mask];
          continue;
        }
        ld &res=dp[next][c][mask];
        int pos=c;
        for(;!(mask&1<<pos);pos=(pos+1)%n);
        
        res=INF;
        rep(i,n)if(i!=pos&&(mask&1<<i)){
          res=min(res,dp[cur][(pos+1)%n][mask^1<<i]*p[pos]
            +dp[cur][(pos+1)%n][mask]*(1-p[pos])+1);
        }
      }
      swap(cur,next);
    }
    return dp[cur][0][(1<<n)-1];
  }
};