Hatena::Grouptopcoder

cafelier@SRM

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

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

 | 

2009-11-05

SRM452

| 23:18 | はてなブックマーク -  SRM452 - cafelier@SRM

SRM452 の成績・ソース (要ログイン) : AC/-/- : rng_58さんの出題だったらしい。面白かったけどむずかった…

500開く

  • 『IとOと?が2500文字以下並んだ文字列が与えられます。?を全部IかOで埋めたときに、等間隔で I...O...I となる部分が1カ所でもあるような埋め方は何通り?』
    • 答えは mod 1000000007 で
  • DPぽい?
  • 左から埋めてって、埋め方の状態毎に可能なパターン数をメモして…
  • うむむ、これだと左にあるIの位置が一カ所でも違うと右のIの置け方かわるから、全然メモで探索が共通化されない…

  • 二カ所固定すると残りの一カ所は決まるから、"最左のIOI部分"の位置は高々2500*2500通り。
  • それぞれについて、「そこが最左のIOIになるような埋め方」を数える…
  • どんだけ頑張ってもさらに*2500くらい時間がかかる気がするぞ。むり。


  • 最左とか考えなければ「そこがIOIになるような埋め方」なら?
    • 2^残りの?の数、だからこれなら簡単に求まるけど
    • 重複が。
    • 包除原理?2500*2500候補あるのに?無理だ

  • まったくわからない
  • スコアボード見てみた。こんだけ時間経ってるのに誰もまだ500解けてない。これは難しいと感じていい問題だ。

  • IOI文字列のみを受理するオートマトンを考える
    • 非決定性オートマトンで2500状態くらいので受理できる
    • 決定化すると2500*2500状態くらい
    • これを使ってメモ化DPすれば…!解けるけど 2500*2500*2500 時間だ。これも無理だ。

  • "IOIじゃない文字列"の方が決めやすいな
    • 左から?を埋めていくときに
      • I は自由に埋めていい
      • O を埋めるときは左のIの鏡像の位置は全部Oで埋める
    • と決めていける
  • なんか意外とIOIじゃない方は(mod 100...007する間でもないくらい)少なかったりするんじゃないか
  • それならそっちを求めて引き算

  • ガリガリと愚直なバックトラック探索を書く
  • はい "?"*2500 で終わりません
  • ていうかそれ以外のケースでも全然答えが合わない

  • ううむもう250を解く時間がない。諦めよう

250開く

  • スコアボード見ると瞬殺してる人が結構いるなあ。
  • 『W*Hのマス目に石を置いていきます。ユークリッド距離2の石ペアがあってはいけません。最大で何個置けますか』

  • ユークリッド距離2というのは、横か縦に1個飛びの場合しかないな。
  • むむ、わからん。置き方全探索?
    • W,H<=1000だ。それは無理だ。
    • なんで瞬殺してる人がいるんだ。

  • greedyに左上から置けるとこ置いてっていいのかな?
    • うーむ、いいのかな。自信ないな。
    • 置けるところに置かなくても最適解を得られたとすると変形してgreedyに置けることに置く最適解にできる…という証明をできるか考えてみたけど、わからん。
    • 置けるところにあえて置かないと後で置ける場所が2カ所増えるし。

  • えーと、1個飛びに影響があるんだから
  • 0 mod 2 のマスと 1 mod 2 のマスへの石の置き方は完全に独立だ
  • ということは、
    • x%2=0,y%2=0のマス目グループ
    • x%2=0,y%2=1のマス目グループ
    • x%2=1,y%2=0のマス目グループ
    • x%2=1,y%2=1のマス目グループ
  • はそれぞれ独立して考えられる。
  • しかもそれぞれ独立して考えると、1個飛びの置き方じゃなくて、「隣接してはいけない」の最大の置き方を考えればいい
  • それは市松模様に置く場合が最大に決まっとる

  • よし解けた
  • 書いた。サンプル通った。submit!

  • (500に戻るも結局分からないまま時間切れ。)

撃墜タイム

  • なんでこんなたくさんの人が500サブミットできてるんだ。すげー。
  • と思ったら僕がウインドウを開くや否や撃墜される人が多数発生
  • みんな撃墜が早すぎるwww

  • 500の残った解答は見てもよくわからないのでスルー
  • 250を見ていく。greedyに埋めている人がいるぞ、これはいいのか…?
    • あれ、いいじゃん!自分の「当たり前に市松模様が最適だ」というのは要するに左上からgreedyに埋めていくのと同じだ。そりゃそうだ。うわあああああああああ

感想

  • そう、だから、250は
bool stone[1000][1000] = {};
int cnt = 0;
for(int y=0; y<H; ++y)
  for(int x=0; x<W; ++x)
     if( (y-2<0 || !stone[y-2][x]) && (x-2<0 || !stone[y][x-2]) )
       ++cnt, stone[y][x]=true;
  • これでいいんだよなあ。うわーうわー。
  • 変な綺麗な計算式より遙かに、とっさにこれが書けるようになりたい。

  • すっかり負け癖がついてるなあ。
  • 年内に2400…いや2500に戻して年を越すことを目標としたい。

SRM452 500

| 15:04 | はてなブックマーク - SRM452 500 - cafelier@SRM

LayCurseさんの記録 と Petr のソースを参考にしながら…うーむ思いつける気がしない。

// mod 演算用ユーティリティ
LL MODVAL = 1000000007LL;
LL ADD(LL x, LL y) { return (x+y)%MODVAL; }
LL SUB(LL x, LL y) { return (x-y+MODVAL)%MODVAL; }
LL MUL(LL x, LL y) { return (x*y)%MODVAL; }
LL POW(LL x, LL e) {
   LL v = 1;
   for(;e;x=MUL(x,x),e>>=1)
      if(e&1)
         v = MUL(v, x);
   return v;
}

// 本体
class IOIString { public:
   string  s;
   LL nI, nQ;

   int countIOIs(vector <string> mask) 
   {
      s  = accumulate( mask.begin(), mask.end(), string("") );
      nI = count( s.begin(), s.end(), 'I' );
      nQ = count( s.begin(), s.end(), '?' );

      return SUB( POW(2,nQ), non_IOI() ); // IOIじゃないものを数えて引く
   }

   LL non_IOI()
   {
      LL ans = 0;
      if( nI == 0 ) ans = ADD(ans,  1); // #I == 0
      if( nI == 0 ) ans = ADD(ans, nQ); // #I == 1
      if( nI == 1 ) ans = ADD(ans,  1); // #I == 1
      for(int D=1; D<s.size(); D+=2)
         for(int S=0; S<D; ++S)
            ans = ADD(ans, non_IOI_2(S, D)); // #I >= 2
      return ans;
   }

   LL non_IOI_2( int S, int D )
   {
      string t;
      for(int i=S; i<s.size(); i+=D)
         t += s[i];

      // t 以外の部分は全部 'O' でないとダメ
      if( count(t.begin(), t.end(), 'I') < nI )
         return 0;

      // t は /O*II+O*/ にマッチしないとダメ。4状態のオートマトンでDP
      LL q0=1, q1=0, q2=0, q3=0;
      for(int i=0; i<t.size(); ++i)
      {
         LL p0 = (t[i]=='O' ?         q0 : t[i]=='I' ?          0 : q0);
         LL p1 = (t[i]=='O' ?          0 : t[i]=='I' ?         q0 : q0);
         LL p2 = (t[i]=='O' ?          0 : t[i]=='I' ? ADD(q1,q2) : ADD(q1,q2));
         LL p3 = (t[i]=='O' ? ADD(q2,q3) : t[i]=='I' ?          0 : ADD(q2,q3));
         q0=p0, q1=p1, q2=p2, q3=p3;
      }
      return ADD(q2, q3);
   }
};
  • "IOIでない文字列"とはどんなものかを考える
    • Iが1個もない
    • Iが1個しかない
    • Iが2個以上の場合は…?
      • I O^奇数 I というパターンはない。そこでIOIが作れてしまう
      • なので O^* I O^2a I O^2b I O^2c ... I O^* という形しかない。(a,b,c,... >= 0)
      • ところが
      • I O^2a I O^2b I だと、両端のIの真ん中がOであってはいけないので、この時必ず a==b
      • というわけで
      • O* (I O^2a)+ I O*
      • というパターンだけが可能
  • このパターンを全部数え上げる。
    • Iどうしの間隔 D=1,3,5,... でループ
      • 最初のIの位置(mod D) S=0,1,2,.,,,,D-1 でループ
        • それぞれの(S,D)でのパターン数をカウント。重複ないので足すだけ。
        • S+iD番目だけ取り出してきた文字列をtとすると、
          • t以外の文字は全て O
          • t自体は O*II...IIO*
        • となってないといけないけれど、この条件は O( |t| )でチェックできる
  • 入力文字列長さを N とすると
  • 一見3重ループなので O(N^3) にみえるけど
  • S<D, O(|t|)<O(N/D) なので</li>
  • 全体の計算量は O(N^2)

SRM452 1000

| 00:06 | はてなブックマーク - SRM452 1000 - cafelier@SRM

rng_58さんのコメントを参考に。

// mod 演算用ユーティリティ
LL MODVAL = 1000000007LL;
LL ADD(LL x, LL y) { return (x+y)%MODVAL; }
LL SUB(LL x, LL y) { return (x-y+MODVAL)%MODVAL; }
LL MUL(LL x, LL y) { return (x*y)%MODVAL; }
LL POW(LL x, LL e) {
   LL v = 1;
   for(;e;x=MUL(x,x),e>>=1)
      if(e&1)
         v = MUL(v, x);
   return v;
}
LL DIV(LL x, LL y) { return MUL(x, POW(y, MODVAL-2)); }
LL C(LL n, LL k) {
   LL v = 1;
   k = min(k, n-k);
   for(LL i=1; i<=k; ++i)
      v = DIV(MUL(v, n-i+1), i);
   return v;
}

// 本体
class IncreasingNumber { public:
   int countNumbers(long long digits, int divisor) 
   {
      LL  N = digits;
      int D = divisor;

      //  ちょうど N 桁の Increasing number
      //=
      //  0<=Σa<=8 な a[] を使って
      //    1*a[0] + 11*a[1] + ... + 111..111*(a[N-1]+1)
      //  の形で書ける数
      //
      //なので
      //
      //  D で割れるちょうど N 桁の Increasing number の個数
      //=
      //  0<=Σa<=8 な a[] で
      //     coeff[0]*a[0] + coeff[1]*a[1] + ... + coeff[N-1]*(a[N-1]+1) = 0
      //  なものの個数 ( where coeff[i] := "1がi+1個並んだもの" % D )


      // ステージ1

      vector<LL> nc(D); // nc[m] = (coeff[i]%D==m な i の数)
      int       ceLast; // coeff[N-1]
      {
         // coeff は高々 D 回でループしてるはず
         vector<int> coeff;
         int loop_start = -1;
         for(int v=1%D ;;)
         {
            loop_start = find(coeff.begin(), coeff.end(), v) - coeff.begin();
            if( loop_start != coeff.size() )
               break;
            coeff.push_back( v );
            v = (v*10 + 1) % D;
         }
         int loop_len = coeff.size() - loop_start;

         for(int i=0; i<coeff.size() && i<N; ++i)
            if( i < loop_start )
               nc[coeff[i]] = 1;
            else
               nc[coeff[i]] = (N-i+loop_len-1)/loop_len % MODVAL;
         ceLast = N-1 < loop_start ? coeff[N-1]
                                   : coeff[(N-1-loop_start)%loop_len+loop_start];
      }


      // ステージ1.5 (毎回計算してると間に合わなかったのでキャッシュ)

      vector< vector<LL> > choice(D, vector<LL>(9));
      for(int m=0; m<D; ++m)
         for(int n=0; n<=8; ++n)
            // nc[m] 種類のものから n 個取る取り方
            choice[m][n] = (n==0 ? 1 : nc[m]==0 ? 0 : C(n+nc[m]-1, nc[m]-1));


      // ステージ2
      // dp[m][n][q] = 余り [0, 1, ... m] の係数から n 個使って総和を余り q にするやり方は何通り?

      vector< vector< vector<LL> > > dp(D, vector< vector<LL> >(9, vector<LL>(D)));
      for(int m=0; m<D; ++m)
         dp[m][0][ceLast] = 1;
      for(int n=1; n<=8; ++n)
         dp[0][n][ceLast] = choice[0][n];
      for(int m=1; m<D; ++m)
         for(int n=1; n<=8; ++n)
            for(int q=0; q<D; ++q)
               for(int k=0,q2=q; k<=n; ++k,q2=(q2-m+D)%D)
                  dp[m][n][q] = ADD( dp[m][n][q], MUL( dp[m-1][n-k][q2], choice[m][k] ) );

      LL ans = 0;
      for(int n=0; n<=8; ++n)
         ans = ADD(ans, dp[D-1][n][0]);
      return ans;
   }
};
  • なんというか、「10進数表記したときに各桁の数字が昇順に並んでいる」っていう性質を「a + 11b + 111c + 1111d + ... (a + b + ... <= 9) と書ける」って捉え直す考え方がなかったです。なるほどなあ。これは綺麗だ!
  • これに気づかせて貰ったらあとはストレートなDP。
    • っと思ったけど結構時間がシビアなことになってしまった。
    • もっと綺麗に書けるのかな
トラックバック - https://topcoder-g-hatena-ne-jp.jag-icpc.org/cafelier/20091105
 | 

presented by cafelier/k.inaba under CC0