Hatena::Grouptopcoder

cafelier@SRM

cafelier のSRM参加記録です。コンテスト中に考えてたことを執拗に全部書き残すとどうなるだろうかという試み本番中にこういうコードが書きたかったなあ、という後で書いた反省コードを書き残す試み

スパムが来たのでしばらくコメント欄をはてなユーザ限定にしています、すみません、

 | 

2008-11-13

SRM425 Div1

| 22:31 | はてなブックマーク -  SRM425 Div1 - cafelier@SRM

SRM425 の成績・ソース (要ログイン) : WA/AC/-- : ひどいポカをした


250 開く

  • 『ロボットが4方向ランダムウォークします。n歩。4方向それぞれ進む確率が与えられる。同じマスを2度踏まない確率は?』

  • 同じマスを踏まないパスを全探索して個数数えるだけだろー
    • n ≦ 14 だと、4^n = 2^28 = 3億弱 の全探索は時間マズいかな?
    • 最初の一歩は対称だから上に進む場合だけに固定して考えればよくて、2^26 だと通るとかいうアレではないか
    • 実装。サンプル通った。submit!
  • (※ Challenge されて落ちました)

500 開く

  • 『5×5のチェス盤に5個以下の駒が配置されてる。全部の駒が連結になるように動かすときのマンハッタン距離での動かし距離総和を最小化』

  • 状態空間は…25C5通り。少ない。幅優先で探索すれば終わる。
  • 実装
    • 盤面の状態は25bitで表せるのでint一個で。
    • 連結判定は毎盤面 Union-Find を走らせて確かめることにした。最近 Union-Find をインラインで書くのが趣味
  • できた。submit

1000 開く

  • 『各枝の壊れる確率が設定されてる無向完全グラフがあります。壊れてちょうど全域木が残る確率は?』
    • 頂点数は16以下

  • 分割統治でダメかな。"頂点 1..8 で全域木が残る確率" × "頂点 9..16 で spanning tree が残る確率" × "1..8 --- 9..16 の辺が一本だけ残る確率" で再帰で解けるような気がする…? 2^16回くらいの計算
    • "辺が一本だけ残る確率"の実装に意外とてまどる。100%壊れる辺とか0%壊れる辺のあたりでゼロ除算の危険があってうざいなー
    • 実装完了した
    • サンプル通らない
  • よく考えたら、1..16 が全域木だからといって、その一部の 1..8 が全域木とは限りませんね、はい。全部バラバラで"1..8 --- 9..16" から8本生き残って全体としては繋がるケースとか、いくらでもある
  • これはダメだ
  • 全然思いつかない

撃墜タイム

  • 開始 1 分で自分の 250 が撃墜されて泣いた
    • 進む確率違うんだから、4方向対称なわけないですよねー。
    • そもそも後ろに戻るパスは有り得ないんだから全部数えても 3^14 で余裕です

感想

  • 250は250なんだからそんな変な工夫がいるはずないのにねえ。1000まだわかってない。あとで考える。

SRM425 Div1 1000 (2008/12/7)

| 17:43 | はてなブックマーク -  SRM425 Div1 1000 (2008/12/7) - cafelier@SRM

425の1000点問題を考える。

一昨日くらいにようやくd:id:tsukunoの言ってたDPというのを理解したので、あんど、O(3^(N-1) * N) にはなるんじゃないだろうかと思ったので組んでみてました。最悪ケース(16頂点、確率0%や100%の辺が一個もない)で3秒ちょい。少し足りない…


#include <vector>
#include <string>
#include <map>
using namespace std;
typedef unsigned int uint;

static const uint OUT=0, IN=1, ROOT=1, LEAF=2, HAVELEAF=0xAAAAAAAA;
#define is(mm,j,t)    (((mm>>(j*2))&3)==(t))
#define isnot(mm,j,t) (((mm>>(j*2))&3)!=(t))

class RoadsOfKingdom
{
public:
  map<uint,double> memo;
  double P[16][16];

  double getProbability(vector <string> roads) 
  {
    // 入力
    int N = roads.size();
    for(int i=0; i<N; ++i)
      for(int j=0; j<N; ++j)
        P[i][j] = (roads[i][j]-'0')/8.0;

    // メモ化再帰
    double ans = 0.0;
    for(uint m=0; m<(1<<N); m+=2) { // すべての全域木mmに関して...
      uint mm = ROOT;
      for(int i=1; i<N; ++i)
        mm |= ((m>>i)&1 ? IN : LEAF) << i*2;
      ans += rec(mm, N); // その全域木ができる確率
    }
    return ans;
  }

  // 部分木mmを、元グラフの各ノードを
  //  (0: mmに入ってない, 1: mmの内部ノード, 2: mmのリーフ)
  // というビットパターンで表す。16頂点なので32bitで足りる。
  double rec(uint mm, int N)
  {
    // trivial cases
    if( mm == ROOT )
      return 1.0;
    if( isnot(mm,0,ROOT) || !(mm&HAVELEAF) )
      return 0.0;

    // memoized?
    map<uint,double>::iterator it = memo.find(mm);
    if( it != memo.end() )
      return it->second;

    // 一番頂点番号の小さいリーフ f をとりのぞく
    int f = 0;
    for(; f<N; ++f)
      if( is(mm,f,LEAF) )
        break;

    // P[f][*] が全部ぶっ壊れる確率
    double alldead = 1.0;
    int numAlive = 0;
    for(int j=0; j<N; ++j)
      if( isnot(mm,j,OUT) )
        if( P[f][j] == 1.0 )
          ++numAlive;
        else
          alldead *= 1.0 - P[f][j];
    if( numAlive >= 2 )
      return 0.0;

    // 「fの親がjだった場合」をmmの全ての内部ノードjについて調べて足し算
    double px = rec( mm^(LEAF<<f*2), N ); // jがf以外にも子供を持ってる場合の確率

    double ans = 0.0;
    for(int j=0; j<N; ++j)
      if( is(mm,j,IN) && P[f][j] != 0.0 )
      {
        double pdead = alldead;
        if( P[f][j] != 1.0 )
          if( numAlive ) continue;
          else pdead /= 1.0 - P[f][j];
        double prest = rec( mm^(LEAF<<f*2)^(3u<<j*2), N ) + px; // fがjの唯一の子の場合
        ans += P[f][j] * pdead * prest;
      }
    return memo[mm] = ans;
  }
};

1.5倍くらいだと小手先の技でもどうにかなりそうな気がしなくもないのですが、まあそれよりはアルゴリズムの改良でなんとかしたい…。そうこうしているうちにEditorialが出てしまったけれど見ないぞー。

実際は 3^(N-1) もなくて、memoに入る状態数は高々120万弱なんですよね。常に最初のLeafを除く方法で出てくる木構造はそのくらいしかない。なのでそういう状態だけもっと直接的に列挙する方法とかがあれば良さそうなんだけど。うむむ


SRM425 Div1 1000

| 15:28 | はてなブックマーク -  SRM425 Div1 1000 - cafelier@SRM

  • あまりに解けないものは Editorial を見るのを解禁することにした
class RoadsOfKingdom { public:
   vector< vector<double> > P;

   double getProbability(vector <string> roads) 
   {
      P     = read_input(roads);
      memo  = vector<double>(1<<P.size(), -1);
      memo0 = vector<double>(P.size()<<P.size(), -1);
      memo1 = vector<double>(P.size()<<P.size(), -1);
      return prob_tree( (1<<P.size())-1 );
   }

   vector< vector<double> > read_input(const vector<string>& R)
   {
      int N = R.size();
      vector< vector<double> > P(N, vector<double>(N));
      for(int i=0; i<N; ++i)
         for(int j=0; j<N; ++j)
            P[i][j] = (R[i][j] - '0') / 8.0;
      return P;
   }

   vector<double> memo;
   double prob_tree(int S)
   {
      if( memo[S] >= 0 )
         return memo[S];

      // the first and the second smallest IDs in S
      int v, w;
      for(v=0;   (1<<v)<=S; ++v) if( S & 1<<v ) break;
      for(w=v+1; (1<<w)<=S; ++w) if( S & 1<<w ) break;

      // if |S| < 2 then it always forms a tree
      if( (1<<w) > S )
         return memo[S] = 1.0;

      // Let's consider v as the "root node" of S, and try all possible "subtrees" T containing w.
      // The situation is (other nodes)=v-(T where w in T)
      double p = 0.0;
      for(int T=S; T; T=(T-1)&S)
         if( (T & 1<<w) && !(T & 1<<v) )
            p += prob_tree(T) * prob_tree(S&~T) * prob_separated(T, S&~T&~(1<<v)) * prob_one(v, T);
      return memo[S] = p;
   }

   double prob_separated(int S1, int S2)
   {
      double p = 1.0;
      for(int v=0; (1<<v)<=S1; ++v) if( S1 & 1<<v )
         p *= prob_zero(v, S2);
      return p;
   }

   vector<double> memo0;
   double prob_zero(int v, int S) // 0 connection between v and S
   {
      const int key = v<<P.size() | S;
      if( memo0[key] >= 0 ) return memo0[key];

      double p = 1.0;
      for(int u=0; (1<<u)<=S; ++u) if( S & 1<<u )
         p *= 1.0 - P[v][u];
      return memo0[key] = p;
   }

   vector<double> memo1;
   double prob_one(int v, int S) // exactly 1 connection between v and S
   {
      const int key = v<<P.size() | S;
      if( memo1[key] >= 0 ) return memo1[key];

      double p = 0.0;
      for(int c=0; (1<<c)<=S; ++c) if( S & 1<<c )
      {
         double q = 1.0;
         for(int u=0; (1<<u)<=S; ++u) if( S & 1<<u )
            q *= u==c ? P[v][u] : 1-P[v][u];
         p += q;
      }
      return memo1[key] = p;
   }
};
  • Leafから行くのではなくRootからトップダウンに行く
  • 「Rootと、そこから繋がっている子供のうち最小のもの(を含むsubtree)」みたいにして集合を分割して再帰を降りると、どうしても O(N 3N) になってしまうので
  • かわりに
  • 「Rootと、最小のもの(を含むsubtree)」と、Rootからの接続を考えない方がよい
  • その辺はともかくとして、とりあえず、
  • 「子供の数が不定の unranked tree でも、ビットDPするときはうまいこと"最初の子供の分だけとる"みたいにして2分割でビットDPが進むようにすると3Nオーダーで行く、という感じのテクニックはunranked treeを扱うときに必須かも
トラックバック - https://topcoder-g-hatena-ne-jp.jag-icpc.org/cafelier/20081113
 | 

presented by cafelier/k.inaba under CC0