Hatena::Grouptopcoder

cafelier@SRM

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

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

 | 

2009-07-24

SRM445

| 02:11 | はてなブックマーク -  SRM445 - cafelier@SRM

SRM445 の成績・ソース (要ログイン) : WA/WA/- : 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL

550開く

  • 『なんかグレイコードみたいな(違うけど)方式で0~k-1までカウントしたときの辞書順最大値を求めてね』
    • 正確には、1つカウントアップする時には 0の桁を複数個選んで1に変える or 1の桁を複数個選んで0に変える ができる
    • そうやってできるもののうち、前とかぶらない範囲で辞書順最小のものがカウントアップ後の数

  • 最初グレイコードじゃね?と勘違いしてビット演算一発→答えが合いません

  • 手で書いてみる
  • 000->001->011->010->110 のように桁があがったステップの次は、最上位桁を残して他全て 0 にできる -> 100。それが常に最小
  • なのでそういうやり方で作る方法を考えればいい
    • kを2進数表記して、
      • 最上位のビットが立ってるとこはそのまま残す
      • 残りの下位桁は再帰的に、0なら全ビット反転して11...11にしたものに対応するコード、0でないなら1引いたものに対応するコード

  • というのでk番目のコードを返すように書いたらまだ答えが合いません
  • 辞書順最大を求めろと書いてあります

  • 微妙に修正して辞書順最大を求めるようにした
  • サンプル通った
  • まあ問題ないでしょう。submit

275開く

  • 『50点ほど格子点に点がばらまかれてます。そいつらからのマンハッタン距離をsortしたときのk番目が最小になるような点の位置(というかそのk番目最小値)を求めてね』

  • ぬぬぬ全然わからん
  • ぬぬぬ
  • ぬぬぬ

  • kが1なら答えは常に0
  • kが2なら、一番距離が近い2点の真ん中
  • kが3なら……
  • ぬぬぬ

  • すごい悩む

  • あー、kがなんであれ、最適解は常にどれかの2点の真ん中にあるような気がする
    • 真ん中でなければ、k番目に近い点の方向に少し動かしてもk番目性は変えずに短くできるはず
    • (※システムテスト曰く間違いらしい…)

  • というコードを書いた。全点ペアの中点を候補として試してみる全探索
  • サンプル通った
  • まあ大丈夫でしょう

1000開く

  • 『アルファベット52文字間の換字暗号(ただしa->aやa->AやA->aのような自分に戻る変換はない)の平文と暗号文が与えられます。平文からそういう暗号文がでてくるような暗号は何通りあるか"mod大きな数"で数えてね』

  • 要は、平文-暗号文に対として現れている文字は固定されるので、現れていない部分の決め方は何通りか。
  • とりあえず現れていない部分をカウントするコードを…

  • さて
    • 単純に探索すると、未割り当ての文字の集合に対してそれぞれのパターン数をメモ化しながら数える
    • 2^52くらいメモ化しないといけないなー無理
  • でも、全部の文字を区別する必要はないな
  • 平文と暗号文に同じ回数ずつ現れてる文字は互いに区別しなくていい
    • 大文字小文字で2個まで現れうるから、
    • 平文と暗号文にそれぞれ0/1/2個現れてるかどうかで9通りにグループ分けすれば、グループ内のそれぞれは対等だから52文字全通り区別して数えるより速そう
    • どっちかが0個のパターンは全部まとめていいかな。6通りにグループ分けすると、たぶん(26+6)C6 くらいしか個数のパターン無いのでメモ化探索でどうにか

  • 書く。
    • すごい場合分けを手作業でやった探索コード書けた
    • あとはメモ化するだけ…
    • キーが6個組のメモ化表作るのに手間取ってるあたりでタイムアップ
    • むむむあと5分あれば…

撃墜タイム

  • 550撃墜された!うそっ

感想

  • 書き取り練習を敢行したい (http://shinh.skr.jp/m/?date=20081221#p01)
  • 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL 1LL
  • 550は1を1LLに変えたら通りました。これはひどい
  • しかし難易度的には550どころか400でも良かった気がする

  • さらに275もシステムテストで死亡
    • ううむ。中点だけだといけないのかな。中"点"だけじゃなくて2等分線全部で距離一定だから2等分線の交点は全て考えないといけないか。そうかも。

SRM445 275

| 11:53 | はてなブックマーク -  SRM445 275 - cafelier@SRM

0.5刻みで全部試すコード。

class TheNewHouseDivOne { public:
  double distance(vector <int> x, vector <int> y, int k) 
  {
    double minD = 1e+99;
    for(double X=-50; X<=+50; X+=0.5)
      for(double Y=-50; Y<=+50; Y+=0.5)
      {
        vector<double> d;
        for(int i=0; i<x.size(); ++i)
          d.push_back( abs(x[i]-X) + abs(y[i]-Y) );
        nth_element(d.begin(), d.begin()+k-1, d.end());
        minD = min(minD, d[k-1]);
      }
    return minD;
  }
};
  • マンハッタン距離なので中線は全て45度線、しかも格子点を通る
  • なので交点は必ず0.5刻み座標に全て乗る

というところか。なるほどなー。

SRM445 1000

| 11:53 | はてなブックマーク -  SRM445 1000 - cafelier@SRM

6個にグループ分けでメモ化探索という方針はあってた


static const LL MODVAL = 1234567891;

class TheEncryptionDivOne { public:
  map<pair< pair<pair<int,int>, int>, pair<pair<int,int>, int> >, LL> memo;

  int count(string msg, string encMsg) 
  {
    // accumulate unused characters
    char cs[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    set<char> ori(cs+0, cs+52), enc(cs+0, cs+52);
    for(int i=0; i<msg.size(); ++i)
      ori.erase(msg[i]), enc.erase(encMsg[i]);

    // obviously-invalid cases
    for(int i=0; i<msg.size(); ++i)
      if( toupper(msg[i]) == toupper(encMsg[i]) )
        return 0;
    if( ori.size() != enc.size() )
      return 0;

    // count
    int t0x=0, tx0=0, t11=0, t12=0, t21=0, t22=0;
    for(char c='a'; c<='z'; ++c) {
      int oc = ori.count(c)+ori.count(c+'A'-'a');
      int ec = enc.count(c)+enc.count(c+'A'-'a');
      if( oc == 0 )      t0x += ec;
      else if( ec == 0 ) tx0 += oc;
      else if(oc==1 && ec==1) t11 ++;
      else if(oc==1 && ec==2) t12 ++;
      else if(oc==2 && ec==1) t21 ++;
      else if(oc==2 && ec==2) t22 ++;
    }
    return rec(tx0, t0x, t11, t12, t21, t22);
  }

  LL rec(int tx0, int t0x, int t11, int t12, int t21, int t22)
  {
    pair< pair<pair<int,int>, int>, pair<pair<int,int>, int> >
      key(make_pair(make_pair(tx0,t0x), t11), make_pair(make_pair(t12,t21), t22));
    if(memo.count(key))
      return memo[key];

    if( t11 )
    {
      LL val = 0;
      if( t0x ) val += t0x *     rec(tx0,   t0x+1-1, t11-1,   t12,   t21,   t22);
      if(t11>1) val += (t11-1) * rec(tx0+1, t0x+1,   t11-1-1, t12,   t21,   t22);
      if( t12 ) val += t12 * 2 * rec(tx0,   t0x+1,   t11-1+1, t12-1, t21,   t22);
      if( t21 ) val += t21 *     rec(tx0+2, t0x+1,   t11-1,   t12,   t21-1, t22);
      if( t22 ) val += t22 * 2 * rec(tx0,   t0x+1,   t11-1,   t12,   t21+1, t22-1);
      return memo[key] = val%MODVAL;
    }
    if( t12 )
    {
      LL val = 0;
      if( t0x ) val += t0x *     rec(tx0,   t0x+2-1, t11,     t12-1,   t21,   t22);
      if( t11 ) val += t11 *     rec(tx0+1, t0x+2,   t11-1,   t12-1,   t21,   t22);
      if(t12>1) val += (t12-1)*2*rec(tx0,   t0x+2,   t11+1,   t12-1-1, t21,   t22);
      if( t21 ) val += t21 *     rec(tx0+2, t0x+2,   t11,     t12-1,   t21-1, t22);
      if( t22 ) val += t22 * 2 * rec(tx0,   t0x+2,   t11,     t12-1,   t21+1, t22-1);
      return memo[key] = val%MODVAL;
    }
    if( t21 )
    {
      LL val = 0;
      if( t0x ) val += t0x *     rec(tx0,   t0x-1, t11+1,   t12,   t21-1,   t22);
      if( t11 ) val += t11 *     rec(tx0+1, t0x,   t11+1-1, t12,   t21-1,   t22);
      if( t12 ) val += t12 * 2 * rec(tx0,   t0x,   t11+1+1, t12-1, t21-1,   t22);
      if(t21>1) val += (t21-1) * rec(tx0+2, t0x,   t11+1,   t12,   t21-1-1, t22);
      if( t22 ) val += t22 * 2 * rec(tx0,   t0x,   t11+1,   t12,   t21-1+1, t22-1);
      return memo[key] = val%MODVAL;
    }
    if( t22 )
    {
      LL val = 0;
      if( t0x ) val += t0x *     rec(tx0,   t0x-1, t11,   t12+1,   t21,   t22-1);
      if( t11 ) val += t11 *     rec(tx0+1, t0x,   t11-1, t12+1,   t21,   t22-1);
      if( t12 ) val += t12 * 2 * rec(tx0,   t0x,   t11+1, t12+1-1, t21,   t22-1);
      if( t21 ) val += t21     * rec(tx0+2, t0x,   t11,   t12+1,   t21-1, t22-1);
      if(t22>1) val += (t22-1)*2*rec(tx0,   t0x,   t11,   t12+1,   t21+1, t22-1-1);
      return memo[key] = val%MODVAL;
    }
    LL f=1;
    for(LL n=1; n<=tx0; ++n)
      f = (f*n)%MODVAL;
    return memo[key] = f;
  }
};

あってたけど、この腐った手作業条件分岐を時間内に正しくデバッグし通せる見込みは全くないなあ。どんだけ時間が切羽詰まっててもちゃんとしたコードを書く習慣を付けなければ…


改訂版

static const LL MODVAL = 1234567891;

class TheEncryptionDivOne { public:
   map<vector<int>, LL> memo;

   int count(string msg, string encMsg) 
   {
      // 平文側(ori)、暗号文側(enc) でそれぞれ
      // 使われてない文字 = これから割り当て決めなきゃいけない文字 を集める
      char cs[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
      set<char> ori(cs+0, cs+52), enc(cs+0, cs+52);
      for(int i=0; i<msg.size(); ++i)
         ori.erase(msg[i]), enc.erase(encMsg[i]);

      // 探索の前にあきらかにダメなケースをはじく
      for(int i=0; i<msg.size(); ++i)
         if( toupper(msg[i]) == toupper(encMsg[i]) )
            return 0;
      if( ori.size() != enc.size() )
         return 0;

      // メモ化探索
      // - 9元ベクトルsが状態。
      // - sは、アルファベット(A-Z)26種を9パターンに分けて持っている
      // - s[a*3+b] == 平文側にa個、暗号文側にb個、未割当の字が残ってるアルファベットの総数
      //    - たとえば入力が "a"->"W" なら初期状態は
      //        (0,0,0,  0,0,1, 0,1,24) 
      //      となる: 1個-2個 のもの (A,aA) が1種、2個-1個の (wW,w)が1種、その他24種
      vector<int> s(9);
      for(char c='a'; c<='z'; ++c)
      {
         int oc = ori.count(c)+ori.count(c+'A'-'a');
         int ec = enc.count(c)+enc.count(c+'A'-'a');
         s[oc*3+ec] ++;
      }
      return rec(s);
   }

   LL rec( const vector<int>& s )
   {
      if( memo.count(s) )
         return memo[s];

      for(int a=1; a<=2; ++a)
      for(int b=0; b<=2; ++b) if( s[a*3+b] ) // 平文側の未割当のものを順に割り当て
      {
         LL val = 0;
         for(int c=0; c<=2; ++c)
         for(int d=1; d<=2; ++d) if( s[c*3+d] ) // 暗号文側の未割当のものを全通り選ぶ
         {
            vector<int> t = s;
            t[a*3+b] --;
            if( LL choices = t[c*3+d]*d )
            {
               t[(a-1)*3+b] ++;
               t[c*3+d]     --;
               t[c*3+(d-1)] ++;
               val += choices * rec(t); // 再帰
            }
         }
         return memo[s] = val%MODVAL;
      }
      return 1;
   }
};
トラックバック - https://topcoder-g-hatena-ne-jp.jag-icpc.org/cafelier/20090724
 | 

presented by cafelier/k.inaba under CC0