Hatena::Grouptopcoder

cafelier@SRM

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

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

 | 

2010-12-20

SRM491

| 17:53 | はてなブックマーク -  SRM491 - cafelier@SRM

SRM491 の成績・ソース (要ログイン) : AC/-/- : ちょっと難しいセットになるとすぐこの成績ですよもうだめだ

250開く

  • 『入力は自然数 N と K。1~Nまでから選んだ異なる数字が各面に書いてある6面ダイスで、裏表の和が3カ所で等しくて、K以上になるようなものは何個あるか』
    • 回転は同一視
    • N, K は 1000 か 2000 とかその辺り以下
  • テストケース作成
    • 最大ケース
    • あと、NやKが小さすぎて0通りになるケースを入れまくっておく

  • 簡単ぽい
  • 式一発で求まりそうだけど
  • NもKも小さいから2乗のオーダまでは地道にループして数えて大丈夫
  • ならそう書いた方が絶対間違えにくい。それで。

  • 和をK以上のいくつにするか固定して(これはK~2Nまで全通り試せばいい)
  • その和になる裏表ペアが何個あるか1~Nまでのループで数えて
  • C(ペア数, 3) を求めて
  • 1~6の場合は2通りとサンプルに書いてあるので回転無視すると2通りなんだろう、2を書ける
  • これを全部足し算

  • できた。
  • 書き書き。
  • いろいろ混乱しながらも書き終える
  • サンプル通った。
  • 230点。ううう時間かけ過ぎ…

600開く

  • 『50文字以下の文字列が最大16個与えられます。各文字列の中身は好きなように並び替えていいので、trieを作った時のノード数を最小にしてね』
  • 16個ということは216をどうにか。
  • 216*50 くらいの計算量かなあ。

  • DPとすると、こうなるはず。
  • 文字列の集合(元々の文字列たちの部分集合)が与えられたときに、
  • これを最適にtrie化する
  • というのは、部分集合の最適trie化がわかってればすぐ解けるか?
  • 解けるか?
  • ええと、
    • 今の部分集合が文字列1個しかなければ、その文字列の長さ分伸ばすしかない。
    • それ以外なら?
    • 全部に同じ文字(たとえば 'a')が含まれてたら、'a' でとりあえず全部まとめるのが明らかに最適で
    • 残りを再帰的に…
    • あれ、残ってるのまた同じ文字列全部の集合だ

  • てことはあれか、そうか、「文字列の集合」と、「trieの上何文字をこの文字列で確定したとしたとき」の最適解を求める
    • さっきのだと、dp(集合,"") = dp("集合","a")+1 になる
  • これだと状態数が…
  • 爆発しませんか…?
  • trieのパスになり得る文字列は、長さ50の文字列がわりと何でも来る
  • ダメだ…

  • 900の方が簡単だったりしないかなあ。
  • と思ってスコアボードを見たら案の定、何人かに瞬殺されてる
  • よし、600は保留。

900開く

  • 『最大50枚ずつ、同数のカードが2セットあります。1から100までの実数が書いてあります。それぞれから1枚ずつ選んでa+bとa*bの大きい方がTaroに、小さい方がHanakoに得点として入ります。k回やったときにTaroの得点/Hanakoの得点が最大になるようにするといくらになりますか』

  • 足し算と掛け算ってどういう条件でどっちが大きくなるんだっけ
    • a+b < ab
    • 0 < ab-a-b
    • 1 < ab-a-b+1
    • 1 < (a-1)(b-1)
  • (a-1) と (b-1) の値による。
  • なのでaもbも2以上なら掛け算がお得。そうでなければケースバイケース。
  • なるほど。わりと綺麗な式になった。
  • きっとこの境界で場合分けすると綺麗に解けるに違いない!

  • とりあえず2以上と2未満に分類して覚えておいて…
  • それでどうするんだ…?

  • うむむわからない。
  • やっぱりHardは無理だ。Mediumに集中しよう。

600に戻る

  • とはいえこちらもわからない。
  • 文字列集合 × そこまでのコンテキスト情報
  • を間に合う計算量で持とうと思うと、コンテキスト情報って50、深さの情報くらいしか持てないけど
  • 深さと集合で最適解求めても、
  • 集合を子ノード用に分割したとき、深さだけだと一貫した最適解が得られるとは限らないのでマージできないし…
  • わからない…

  • ええい乱数アタックだ!
  • 16個の文字列をランダムに並び替える。
  • 左から順にgreedyに最適にマージしていく。
  • 16! 全通りは勿論間に合わないけど、結構な割合時間内でカバーできるでしょう

  • で、最適にマージって。
  • まずそもそも最初の一個目の文字列をどうしよう。
  • 完全に trie にしてしまうとどの順で並べるべきかは50!(までは行かないけど、26文字種しかないので)通りなので、
  • これも乱数にするとさすがに無理だ
  • ここは、trieを拡張して、leafには文字列集合が入るようにする
    • そこから下はlazyにあとでマージするときに伸ばす
  • 拡張trie と文字列(というか文字集合)が与えられたときに最適マージ
  • すでに集合じゃなくて展開されてるedgeに沿って一番深く行けるところまで伸ばして、そこに集合をぶらさげ…
  • leafまでいったら集合と集合のmergeで、
  • これはできる限り共通部分を下に垂らして最後で分離
  • 共通部分を下に垂らすのは…
  • うおおここの順番も後で最適マージするのに重要だ
  • これも集合にした拡張trie

  • とかやってると、最後まで問題の情報を単にencodeしただけの謎データ構造が残るだけだなあ。
  • 無理だ
  • わからない…

感想

  • というわけでタイムアップ。
  • ひどい結果…
  • 900は、その後も時間内にはわからなかった気がするけど、
  • しかし2部グラフのマッチングに一瞬たりとも見えなかったのは酷いというレベルじゃ済まないなあ
  • 足し算と掛け算というスコアシステムのせいで、数論っぽい気の利いた最適化か!と釣られてしまった
  • 反省

SRM491 600

| 11:38 | はてなブックマーク -  SRM491 600 - cafelier@SRM

  • 上手い問題だなあ
class PrefixTree { public:
   static vector<int> wordToFreq( const string& word )
   {
      vector<int> freq(26);
      for(int i=0; i<word.size(); ++i)
         freq[word[i]-'a']++;
      return freq;
   }

   static vector< vector<int> > wordsToFreqs( const vector<string>& words )
   {
      vector< vector<int> > freqs;
      transform(words.begin(), words.end(), back_inserter(freqs), &wordToFreq);
      return freqs;
   }

   static int commonPrefix(const vector< vector<int> >& freqs, int s)
   {
      vector<int> common;
      for(int first=1,i=0; (1<<i)<=s; ++i)
         if( s & (1<<i) )
            if( first ) {
               common = freqs[i];
               first = 0;
            } else {
               for(int k=0; k<26; ++k)
                  common[k] = min(common[k], freqs[i][k]);
            }
      return accumulate(common.begin(), common.end(), 0);
   }

   int getNumNodes(vector<string> words) 
   {
      const int N = words.size();
      const vector< vector<int> >& freqs = wordsToFreqs(words);

      vector<int> dp(1<<N);
      for(int s=1; s<(1<<N); ++s)
      {
         dp[s] = 1;
         for(int i=0; i<N; ++i)
            if( s & (1<<i) )
               dp[s] += words[i].size();

         int cp = commonPrefix(freqs, s);
         for(int ss=s-1; ss&=s; --ss)
            dp[s] = min(dp[s], dp[ss]+dp[s&~ss]-cp-1);
      }
      return dp[(1<<N)-1];
   }
};
  • 文字列集合 s から作る trie のベストスコアは
  • s = ss ∪ (s/ss) に二分割して
    • ssのベストスコア + (s/ss)のベストスコア - s全体の共通部分
  • になる(直感的にはなりそうだけど証明できてない…)
  • ということに気づけばこれをDPでそのまま書くだけ。

SRM491 900

| 14:38 | はてなブックマーク -  SRM491 900 - cafelier@SRM

  • この最小費用流、おそいぞ…
template<typename T>
class IdGen
{
   map<T, int> v2id_;
   vector<T>   id2v_;
public:
   int v2id(const T& v)
   {
      if( !v2id_.count(v) )
      {
         v2id_[v] = size();
         id2v_.push_back(v);
      }
      return v2id_[v];
   }
   const T& id2v(int i) const { return id2v_[i]; }
   int size() const { return id2v_.size(); }
};

template<typename Vert, typename Cost, typename Flow, int NV=256>
class MinCostFlow
{
   IdGen<Vert> idgen;

   vector<int> G[NV];
   Flow F[NV][NV];
   Cost C[NV][NV];

public:
   void addEdge( Vert s_, Vert t_, Cost c, Flow f )
   {
      int s = idgen.v2id(s_), t = idgen.v2id(t_);
      G[s].push_back(t);
      G[t].push_back(s);
      C[s][t] = c;
      C[t][s] = -c;
      F[s][t] = f;
      F[t][s] = 0;
   }

   pair<Cost, Flow> calc( Vert s_, Vert t_ )
   {
      const int N=idgen.size(), S=idgen.v2id(s_), T=idgen.v2id(t_);
      static const Cost COST_INF = 1e+300; // !!EDIT HERE!!
      static const Flow FLOW_INF = 0x7fffffff;


      Cost total_cost = 0;
      Flow total_flow = 0;
      vector<Cost> h(N, 0); // potential
      for(Flow RF=FLOW_INF; RF>0; ) // residual flow
      {
         // Dijkstra -- find the min-cost path
         vector<Cost> d(N, COST_INF);  d[S] = 0;
         vector<int>  prev(N, -1);

         typedef pair< Cost, pair<int,int> > cedge;
         priority_queue< cedge, vector<cedge>, greater<cedge> > Q;
         Q.push( cedge(0, make_pair(S,S)) );
         while( !Q.empty() ) {
            cedge e = Q.top(); Q.pop();
            if( prev[e.second.second] >= 0 )
               continue;
            prev[e.second.second] = e.second.first;

            int u = e.second.second;
            for(int i=0; i<G[u].size(); ++i) {
               int  v = G[u][i];
               Cost r_cost = C[u][v] + h[u] - h[v];
               if( F[u][v] > 0 && d[v] > d[u]+r_cost )
                  Q.push( cedge(d[v]=d[u]+r_cost, make_pair(u,v)) );
            }
         }

         if( prev[T] < 0 )
            break; // Finished

         // As much as possible
         Flow f = RF;
         for(int u=T; u!=S; u=prev[u])
            f = min(f, F[prev[u]][u]);
         RF         -= f;
         total_flow += f;

         for(int u=T; u!=S; u=prev[u])
         {
            total_cost    += f * C[prev[u]][u];
            F[prev[u]][u] -= f;
            F[u][prev[u]] += f;
         }

         // Update the potential
         for(int u=0; u<N; ++u)
            h[u] += d[u];
      }
      return make_pair(total_cost, total_flow);
   }
};

class FoxCardGame { public:
   double theMaxProportion(vector <double> pileA, vector <double> pileB, int k) 
   {
      double L=1, R=50;
      for(int i=0; i<50; ++i)
         (possible(pileA, pileB, k, (L+R)/2) ? L : R) = (L+R)/2;
      return L;
   }

   enum Tag {Src, Left, Right, Target, Goal};
   bool possible(vector <double> pileA, vector <double> pileB, int k, double R)
   {
      MinCostFlow<pair<Tag,int>, double, int> mcf;

      for(int a=0; a<pileA.size(); ++a)
         mcf.addEdge( make_pair(Src,0), make_pair(Left,a), 0.0, 1 );
      for(int a=0; a<pileA.size(); ++a)
      for(int b=0; b<pileB.size(); ++b)
      {
         double x_ = pileA[a]+pileB[b], y_ = pileA[a]*pileB[b];
         double x = max(x_,y_), y = min(x_,y_);
         mcf.addEdge( make_pair(Left,a), make_pair(Right,b), R*y-x+10000.0, 1 );
      }
      for(int b=0; b<pileB.size(); ++b)
         mcf.addEdge( make_pair(Right,b), make_pair(Target,0), 0.0, 1 );
      mcf.addEdge( make_pair(Target,0), make_pair(Goal, 0), 0.0, k );

      return mcf.calc( make_pair(Src,0), make_pair(Goal,0) ).first <= 10000.0*k;
   }
};
  • 「二分探索で Σx / Σy ≧ R にできる R の最大値を探す」
  • 式変形すると「Σ(Ry-x) ≦ 0 にできる R の最大値を探す」
  • つまり「Σ(Ry-x) を最小にする」
  • 「最小費用流」
  • それはいいんだけど、2secギリギリ近くかかる。
  • 最小費用流の改良しておいた方がいいかなあ。
トラックバック - https://topcoder-g-hatena-ne-jp.jag-icpc.org/cafelier/20101220
 | 

presented by cafelier/k.inaba under CC0