cafelier のSRM参加記録です。コンテスト中に考えてたことを執拗に全部書き残すとどうなるだろうかという試み本番中にこういうコードが書きたかったなあ、という後で書いた反省コードを書き残す試み
スパムが来たのでしばらくコメント欄をはてなユーザ限定にしています、すみません、
SRM | |
SRM493 の成績・ソース (要ログイン) : AC/AC/- : 撃墜祭り回なので順位はよかった万歳
今回からは方向性を変えて、「こんなコードが本番中に書けたら理想的なんじゃないかなあ、と自分が思うコード」を、そう思う理由とともに並べる参加記になろうと思います。一回で飽きる可能性があります。
class AmoebaCode { public: // メインルーチン int find(string code, int K) { int answer = 1; for(int D=2; D<=K; ++D) if( can(code, K, 0, string(D-1,'X')) ) // 最小距離が D 以上になるようにできる? answer = max(answer, D); return answer; // できるような D の最大値を返す } // 「code[i..$) に 1 ~ K を埋められるか?」を再帰的に判定 // prev には code[i] の直前 D-1 文字を入れて呼び出す。 // この D-1 文字は避けないと最小距離 D 以上にならないので避けましょう map<pair<size_t,string>, bool> memo; bool can(const string& code, const int K, size_t i, const string& prev) { if( i == code.size() ) return true; // 最後まで埋まった。OK! pair<int,string> key(i, prev); if( memo.count(key) ) return memo[key]; // メモ化。すでに見た引数なら覚えてる結果を返す // code[i] に入れる文字の候補を列挙 vector<char> cand; if( code[i] == '0' ) // '0' なら 1 から K のどれに置き換えてもいい for(int k=1; k<=K; ++k) cand.push_back( char('0'+k) ); else // それ以外なら置き換えられない cand.push_back( code[i] ); bool ok = false; for(int k=0; k<cand.size(); ++k) // 候補を全部試してみる if( count(prev.begin(), prev.end(), cand[k]) // prev を避けてて && can(code, K, i+1, prev.substr(1)+cand[k]) ) // code[i+1..$) も全部埋められればOK ok |= true; return memo[key] = ok; // メモ化の表に記憶しつつ return } };
int find(string code, int K) { for(int D=K; D>=2; --D) if( can(code, K, 0, string(D-1,'X')) ) return D; return 1; }
bool can(const string& code, const int K, size_t i, const string& prev) { ... for(int k=0; k<cand.size(); ++k) if( count(prev.begin(), prev.end(), cand[k]) && can(code, K, i+1, prev.substr(1)+cand[k]) ) return memo[key] = true; return memo[key] = false; }
class StonesGame { public: string winner(int N, int M, int K, int L) { if( isOneStep(N,M,K,L) ) // M から L に一手でいけるなら先手の勝ち return "Romeo"; for(int x=1; x<=N; ++x) if( isOneStep(N,M,K,x) && !isOneStep(N,x,K,L) ) return "Draw"; // 先手も後手も勝てない場合 return "Strangelet"; // M からは「Lに一手で行けちゃうx」にしか行けないなら、後手の勝ち } bool isOneStep(int N, int M, int K, int L) { int d = abs(M-L); if( d<K && (d%2)!=(K%2) ) { int mid = (M+L)/2; // M と L の真ん中を中心に int e = mid + K/2; // 右に K/2 int b = e - K+1; // 左に K/2 伸ばした範囲をreverseすればMがLに行けるはず… return 1<=b && e<=N; // そんな範囲で大丈夫か } return false; } };
presented by cafelier/k.inaba under
cafelier2011/01/15 00:54300はTayamaさんの方法 https://topcoder-g-hatena-ne-jp.jag-icpc.org/Tayama/20110114/1294953710 が美しいなあ。「1手で行けるところの全列挙」をO(N)で書くのは安全度高いので、それで済むならそう書きたくて、で、そうか、それを両側探索みたいにして使えばいいのか!