ラベル Google Code Jam の投稿を表示しています。 すべての投稿を表示
ラベル Google Code Jam の投稿を表示しています。 すべての投稿を表示

2014年4月15日火曜日

Google Code Jam 2014 Qualification Round

今年も GCJ に参加しているわけですが、無事、Qualification Round は突破した模様です。一応全完。今回も C++11 で書こう、は有効の状態です。あまり活用してないですが。まず最初にテンプレ。

// gcc version 4.8.2 with -std=c++11
 
#include <iostream>
#include <sstream>
#include <iomanip>
 
#include <iterator>
 
#include <algorithm>
#include <numeric>
#include <utility>
#include <limits>
 
#include <string>
 
#include <vector>
#include <deque>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <stack>
 
#include <tuple>
#include <initializer_list>
 
#include <cmath>
 
// Boost library can be retrieved from http://www.boost.org/
// 1.52 is used
 
#pragma GCC diagnostic ignored "-Wconversion"
#include <boost/range/irange.hpp>
#include <boost/range/iterator_range.hpp>
#pragma GCC diagnostic warning "-Wconversion"
 
typedef unsigned long long ULL;
typedef long long LL;
typedef unsigned long UL;
typedef unsigned int UI;
typedef unsigned short US;
typedef unsigned char UC;
 
#define RNG(v) (v).begin(), (v).end()
#define REP(v, e) for(UI v = 0U; v < e; ++v)
#define REP_(v, s, e) for(UI v = s; v < e; ++v)
#define REPV(v, e) for(v = 0; v < e; ++v)
#define REPV_(v, s, e) for(v = s; v < e; ++v)
 
using namespace std;
 
template<class Integer>
inline auto
IR(Integer first, Integer  last) -> decltype(boost::irange(first, last))
{ return boost::irange(first, last); }
 
int main(void)
{
    ios_base::sync_with_stdio(false);
 
    UI cases; cin >> cases;
    REP(casenum, cases) {
        cout << "Case #" << casenum+1 << ": " << endl;
    }
 
    return 0;
}

Range-based for loop 用に IR() を作ってます。元々 REP マクロ(というかマクロ全般)があまり好きではないので基本 Range-based for loop 使用ですが元々のテンプレで使っていた部分から外すほど修正もしていない感じです。では、以下各問題について。

Problem A. Magic Trick

やるだけ。わざわざ set 2 つ作って set 演算してますが、1 つだけ作っといてもう一方は読み込みながら判定、の方が無駄が少ないですね。後、サイズ既知の vector の場合は for(auto &val : v) { cin >> v; } でさくっと読み込めますけどこれくらい簡単に set とかも読み込みたい感じ。inserter みたいな感じでラッパ作ればいけそうではありますが。

set<int> read()
{
    set<int> t;
    int ans; cin >> ans;
    for(int i : IR(1,5)) {
        int n;
        if(i != ans) {
            for(int j: IR(0,4)) { cin >> n; }
        } else {
            for(int j: IR(0,4)) { cin >> n; t.insert(n); }
        }
    }
    return t;
}
 
int main(void)
{
    ios_base::sync_with_stdio(false);
 
    UI cases; cin >> cases;
    REP(casenum, cases) {
        set<int> s1 = read();
        set<int> s2 = read();
        set<int> sr;
        set_intersection(RNG(s1), RNG(s2), inserter(sr, sr.end()));
        if(sr.size() > 1) {
            cout << "Case #" << casenum+1 << ": " << "Bad magician!" << endl;
        } else if(sr.size() == 0) {
            cout << "Case #" << casenum+1 << ": " << "Volunteer cheated!" << endl;
        } else {
            cout << "Case #" << casenum+1 << ": " << *sr.begin() << endl;
        }
    }
 
    return 0;
}

Problem B. Cookie Clicker Alpha

ループで回した場合のループ回数を確認するのが面倒だったのでへたれて不等式を解いて一撃にしてしまいました。$$ X/{2+(n+1)F} ≤ C/{2+(n+1)F} + X/{2+(n+2)F} $$ n と n+1 で立式すればよかったんですが、なぜか n+1 と n+2 で立ててしまったので微妙にずれた感じです。一応、累積誤差を考えて数が小さい方から加算するようにしていますが気にしなくても良かったかも。

int main(void)
{
    ios_base::sync_with_stdio(false);
 
    UI cases; cin << cases;
    REP(casenum, cases) {
        long double C, F, X; cin << C << F << X;
        int thr = floor((F*X - 2*C - F*C)/(F*C));
        long double result = 0;
        if(thr <= 0) {
            result += X / (2+(thr+1)*F);
            for(int i : IR(0, thr+1)) {
                result += C / (2 + (thr-i)*F);
            }
        } else {
            result = X / 2;
        }
        cout << "Case #" << casenum+1 << ": " << fixed << setprecision(7) << result << endl;
    }
 
    return 0;
}

Problem C. Minesweeper Master

Cookie Clicker には Orteil does not endorse and has no involvement with Google Code Jam. と書いてあるのに Minesweeper には Microsoft Windows への言及があるのに同様の文言がなかったのは何か意味があるのでしょうか。

DP で解いた人も居るようですが、場合わけでやりました。書いてる時には(コピペ分も多いので)そんなに長く書いたつもりはなかったのですが、結果としては結構長いですね。

全体を貫く考え方としては、(1行、あるいは1列の場合でなければ)空きマスが最低2行あるいは2列繋がっていないと広がらない、です。また、c は必ず左上にしています(一般性を失わない)。最初の場合分けとしては、1行の場合、1列の場合、2行の場合、2列の場合、それ以外(3行3列以上)の場合で分けています。行・列を転置してしまえば場合わけは少なくなりますが、転置するのも面倒なんでコピペベースでいいや、という駄目な手抜きです。

1行あるいは1列の場合は、impossible は無しで右端あるいは下端から詰めるだけ。

2行あるいは2列の場合は爆弾が奇数の場合は基本 impossible ですが、1 マスだけ残っている場合は OK。OK の場合は右端あるいは下端から詰めるだけです。

最後、3行3列以上の場合は、さらに場合わけしています。最低2行あるいは2列必要ということから考えると、1) M ≦ (R-2)*(C-2) の場合は、左端および上端2列分を開けておいて右下から詰めていけば良い 2) R*C-M < 9 の場合、R*C-M = 2, 3, 5, 7 の場合 impossible、それ以外の場合は左上の状態は R*C-M の値に合わせて決め打ち、で良いことが分かります。最後、R*C-M ≧ 9 の場合ですが、空きマス8個分は確定、(2,2) の位置で偶奇を調整して、後は右2列を下から1行(2マス)づつ、上2行を右から1列(2マス)づつ埋めていけば良いことになります。

Short input については全入力パターンが (1+2+3+4+5)^2 = 225 通り、制約の数字から全パターンが入力になってそうってことでローカルで全入力作っといて目視で確認してから subumit してます。時間に余裕のある Qualification Round ならではって感じですね。

void c1(int R, int M)
{
    cout << "c" << endl;
    for(int i: IR(0, R - M - 1)) cout << "." << endl;
    for(int i: IR(0, M)) cout << "*" << endl;
}
 
void r1(int C, int M)
{
    cout << "c";
    for(int i: IR(0, C - M - 1)) cout << ".";
    for(int i: IR(0, M)) cout << "*";
    cout << endl;
}
 
void c2(int R, int M)
{
    if(2*R-M == 1) {
        cout << "c*\n";
        for(int i: IR(0, R - 1)) cout << "**" << endl;
    } else if(M % 2 || 2*R-M == 2) {
        cout << "Impossible" << endl;
    } else {
        cout << "c.\n";
        for(int i: IR(0, R - M/2 - 1)) cout << ".." << endl;
        for(int i: IR(0, M/2)) cout << "**" << endl;
    }
}
 
void r2(int C, int M)
{
    if(2*C-M == 1) {
        cout << "c";
        for(int i: IR(0, M/2)) cout << "*";
        cout << endl;
        cout << "*";
        for(int i: IR(0, M/2)) cout << "*";
        cout << endl;
    } else if(M % 2 || 2*C-M == 2) {
        cout << "Impossible" << endl;
    } else {
        cout << "c";
        for(int i: IR(0, C - M/2 - 1)) cout << ".";
        for(int i: IR(0, M/2)) cout << "*";
        cout << endl;
        cout << ".";
        for(int i: IR(0, C - M/2 - 1)) cout << ".";
        for(int i: IR(0, M/2)) cout << "*";
        cout << endl;
    }
}
 
char result[50][50];
void gen(int R, int C, int M)
{
    for(int i: IR(0,R)) { for(int j: IR(0,C)) { result[i][j] = '*'; } }
    if(R*C - M == 1) { // nothing to do
    } else if(R*C - M == 4) {
        result[0][0] = result[0][1] = result[1][0] = result[1][1] = '.';
    } else if(R*C - M == 6) {
        result[0][0] = result[0][1] = result[1][0] = result[1][1] = result[2][0] = result[2][1] = '.';
    } else if(R*C - M == 8) {
        for(int i: IR(0,3)) { for(int j: IR(0,3)) { result[i][j] = '.'; } }
        result[2][2] = '*';
    } else if(R*C - M < 9) {
        cout << "Impossible" << endl;
        return;
    } else if(M <= (R-2)*(C-2)) {
        for(int i: IR(0,2)) { for(int j: IR(0,C)) { result[i][j] = '.'; } }
        for(int i: IR(0,R)) { for(int j: IR(0,2)) { result[i][j] = '.'; } }
        int MM = (R-2)*(C-2);
        for(int i: IR(2, R)) {
            if(M == MM) break;
            for(int j: IR(2,C)) {
                result[i][j] = '.'; --MM;
                if(M == MM) break;
            }
        }
    } else {
        for(int i: IR(0,3)) { for(int j: IR(0,3)) { result[i][j] = '.'; } }
        int MM = R*C - 9;
        if(((R*C-M) % 2) == 0) {
            result[2][2] = '*'; ++MM;
        }
        for(int i: IR(3, R)) {
            if(M == MM) break;
            for(int j: IR(0, 2)) {
                result[i][j] = '.';
            }
            MM-=2;
        }
        for(int i: IR(3, C)) {
            if(M == MM) break;
            for(int j: IR(0, 2)) {
                result[j][i] = '.';
            }
            MM-=2;
        }
    }
    result[0][0] = 'c';
    for(int i: IR(0,R)) { for(int j: IR(0,C)) { cout << result[i][j]; } cout << endl; }
}
 
int main(void)
{
    ios_base::sync_with_stdio(false);
 
    UI cases; cin >> cases;
    REP(casenum, cases) {
        int R, C, M; cin >> R >> C >> M;
        cout << "Case #" << casenum+1 << ":" << endl;
        if(R == 1) { r1(C, M); }
        else if(C == 1) { c1(R, M); }
        else if(R == 2) { r2(C, M); }
        else if(C == 2) { c2(R, M); }
        else { gen(R, C, M); }
    }
 
    return 0;
}

Problem D. Deceitful War

War について Ken の戦略は Naomi の戦略に依らないということなので順序は気にしなくとも良いはず。ということで、Ken としては勝つときには最小の余力(Naomi の値以上で最小のものを出す)で勝ち、負けるときは最小で出すという自然な戦略で良いはずです。では、Deceitful War ではどうなのかといことで、とりあえず最初にできるだけ強いカードを捨てさせて(相手の最大に自分の最小をぶつける)、後は先後逆転で計算する形で書いたら結果が合いません(この時点でなんで先後逆転で計算しようと思ったのか細かいことを忘れちゃってます)。入力例の最後をソートして眺めると、勝ちようがない最小の1枚だけ捨てて後は Naomi が全て勝ってるんですよね。ってことで捨てる枚数でループさせて最大値を取る、で一応結果は合うのですが……。ここで天啓。「あれ?相手のカードも戦略も全部分かってるんだから相手の出すカード全部操作できるんじゃね?」これで先後逆転で一発計算すればいいや、となりました。実際には submit 時点では宣言した値と勝敗が異なってはいけないという条件を忘れていたので考えが足りなかったのですが、Ken の最大値より大きな値を言い続ければ最小から出てくるのでそこで勝ち続けて、勝てなくなるところからは最小値より小さな値を言い続ければやっぱり小さい方から出てきてそこで負ければよい、となるので問題ないことになります(仮に本当にこのまま実際にに人間相手にやったら普通ばれるでしょうけれども)。

int calc_war(const set<double> &naomi, set<double> ken)
{
    int ret = 0;
    for(double i : naomi) {
        auto it = ken.upper_bound(i);
        if(it != ken.end()) {
            ken.erase(it);
        } else {
            ken.erase(ken.begin());
            ++ret;
        }
    }
    return ret;
}
 
int main(void)
{
    ios_base::sync_with_stdio(false);
 
    UI cases; cin >> cases;
    REP(casenum, cases) {
        set<double> naomi, ken;
        int N; cin >> N;
        for(int i: IR(0, N)) { double t; cin >> t; naomi.insert(t); }
        for(int i: IR(0, N)) { double t; cin >> t; ken.insert(t); }
        size_t result_war = calc_war(naomi, ken);
        size_t result_dwar = naomi.size() - calc_war(ken, naomi);;
        cout << "Case #" << casenum+1 << ": " << result_dwar << ' ' << result_war << endl;
    }
 
    return 0;
}

まとめ

TopCoder Open より Google Code Jam の方が相性がいい感じなのですが、なかでも Qualification Round は時間を気にせずに考えたり書けるんで一番楽しいです。本戦考えるとあんまり良くない参加方法ではありますが。今年もどこまでいけるか分かりませんが、Round1 は突破したいなぁ。

2013年4月14日日曜日

Google Code Jam 2013 Qualification Round

結果

D-Large が Timeout した以外は全部提出しましたが、C-Large が両方落ちて結局 100pt 4217 位でした。落ちる前は 3 桁 rank だったので終了後スコア見たときには D-Large 解いた人そんないるの?と思ったりしてしまいました。C-Large は 15 桁くらいまで bruteforce したリストと照らし合わせた上だったので落ちると思っていなかったのです。が、まぁ、最終結果で確認しろってことですね(後述)。

前振り

今回も C++11 を使って書く、をコンセプトにしています。開始してから gcc version 4.8.1 20130328 (prerelease) (GCC) をダウンロードしてきて使っています。ついでにとりあえず普段は使わない 64 bit exe にしています。共通コードは以下のようなコードです。Boost も使用。Google Code Jam の規定用に URL とバージョンを明示しています。IR というのは range-base for loop 用のものです(Integer Range のイメージ)。for(auto i : IR(1, 6)) とかやると 1,2,3,4,5 でループされます。

// gcc version 4.8.1 20130328 (prerelease) (GCC) at http://www.drangon.org/mingw/
// with -std=c++11

#include <iostream>
#include <sstream>
#include <iomanip>

#include <iterator>

#include <algorithm>
#include <numeric>
#include <utility>
#include <limits>

#include <string>

#include <vector>
#include <deque>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <stack>

#include <tuple>
#include <initializer_list>

#include <cmath>

// Boost library can be retrieved from http://www.boost.org/
// 1.52 is used

#pragma GCC diagnostic ignored "-Wconversion"
#include <boost/range/irange.hpp>
#include <boost/range/iterator_range.hpp>
#pragma GCC diagnostic warning "-Wconversion"

typedef unsigned long long ULL;
typedef long long LL;
typedef unsigned long UL;
typedef unsigned int UI;
typedef unsigned short US;
typedef unsigned char UC;

#define RNG(v) (v).begin(), (v).end()
#define REP(v, e) for(UI v = 0U; v < e; ++v)
#define REP_(v, s, e) for(UI v = s; v < e; ++v)
#define REPV(v, e) for(v = 0; v < e; ++v)
#define REPV_(v, s, e) for(v = s; v < e; ++v)

using namespace std;

template<class Integer>
inline boost::iterator_range< boost::range_detail::integer_iterator<Integer> >
IR(Integer first, Integer  last)
{ return boost::irange(first, last); }

Problem A. Tic-Tac-Toe-Tomek

縦横斜めで各コマの数を数えて判定。Small input の時は↓のように書きましたが、

int main(void)
{
 ios_base::sync_with_stdio(false);

 UI cases; cin >> cases; cin.ignore(numeric_limits<streamsize>::max(), '\n');
 REP(casenum, cases) {
  string result;
  int num = 0;
  int counter[4+4+2][3] = { 0 };
  REP(i, 4) {
   string s;
   getline(cin, s);
   REP(j, 4) {
    if(s[j] == '.') continue;
    int t = 0;
    ++num;
    switch(s[j]) {
    case 'X': t = 0; break;
    case 'O': t = 1; break;
    case 'T': t = 2; break;
    default: assert(false);
    }
    ++counter[  i][t];
    ++counter[4+j][t];
    if(  i == j) ++counter[8][t];
    if(3-i == j) ++counter[9][t];
   }
  }
  REP(i, 10) {
   if(counter[i][0] + counter[i][2] == 4) result = "X won";
   if(counter[i][1] + counter[i][2] == 4) result = "O won";
  }
  if(!result.size()) {
   if(num == 16) result = "Draw";
   else result = "Game has not completed";
  }
  cin.ignore(numeric_limits<streamsize>::max(), '\n');
  cout << "Case #" << casenum+1 << ": " << result << endl;
 }

 return 0;
}

「C++11で書いてないじゃないですかー」ということから一部修正して large input を submit

map<char, int> table = { {'X', 0 }, {'O', 1 }, {'T', 2 } };
// 略
    int t = table[s[j]]; // swtich のところを置き換え

書くだけならこうも書けますが

    int t = map<char, int>{ {'X', 0 }, {'O', 1 }, {'T', 2 } }[s[j]];

毎回 map 作られるのはどうかと思ったのでやめました。

Problem B. Lawnmower

高い方のマスから縦横の高さを確定していく、というコードで1回書いて、別に縦横両方ともその高さで刈る訳じゃないじゃんということで没。実際に提出したコードは↓。縦横で最大の高さをとって(これ以上低く刈ることはできない)、各マスに対して縦横の低い方を取った結果が指定に一致するか、で判定しています。これ C++03 でも通っちゃうんじゃ?と思いましたが、right angle bracket さん(template 指定としての >> の連続)がいるので大丈夫ですね。

int main(void)
{
 ios_base::sync_with_stdio(false);

 UI cases; cin >> cases;
 REP(casenum, cases) {
  UI N, M; cin >> N >> M;
  vector<vector<int>> board(N, vector<int>(M, 0));
  vector<int> h(N, 1), v(M, 1);
  REP(i, N) {
   REP(j, M) {
    cin >> board[i][j];
    h[i] = max(h[i], board[i][j]);
    v[j] = max(v[j], board[i][j]);
   }
  }
  std::string result = "YES";
  REP(i, N) {
   REP(j, M) {
    if(board[i][j] != min(h[i], v[j])) { result = "NO"; break; }
   }
   if(result == "NO") break;
  }
  cout << "Case #" << casenum+1 << ": " << result << endl;
 }

 return 0;
}

Problem C. Fair and Square

問題のアレです。繰り上がりが発生しないケースで限定しています。abcba の二乗だと、

876543210
a22ab2ca+b22ab+2bc2a2+2b2+c22ab+2bc2ca+b22aba2

abccba の二乗だと、

109876543210
a22ab2ca+b22bc+2ca2ab+2bc+c22a2+2b2+2c22ab+2bc+c22bc+2ca2ca+b22aba2

と、偶数桁と奇数桁で挙動が違いますが中央桁の 2a2+2b2+2c2 あるいは 2a2+2b2+c2 が一番効いてきます。結局ほとんど 0 と 1 場合によっては 2 もありうるという感じになります。これをパターン分けして数え上げて最初にリストを作ってしまう、というコードが↓です。

vector<string> fair = { "1", "4", "9", "121", "484" };

struct numeric_less
{
 bool operator()(const string &s1, const string &s2) const {
  return s1.size() < s2.size() ||
   (s1.size() == s2.size() && s1 < s2);
 }
};

void mygenerate(int len, const vector<pair<int,int>> &indices)
{
 bool valid = true;
 vector<int> v(len);
 for(auto &val : indices) {
  v[val.first * 2] += val.second * val.second;
  if(v[val.first * 2] > 9) { valid = false; break; }
 }
 REP(i, indices.size()) {
  for(auto j : IR<UI>(i+1, indices.size())) {
   v[indices[i].first+indices[j].first] += 2 * indices[i].second * indices[j].second;
   if(v[indices[i].first+indices[j].first] > 9) { valid = false; break; }
  }
  if(!valid) break;
 }
 if(valid) {
  string s;
  for(auto &val : v) {
   s.push_back(val+'0');
  }
  fair.push_back(s);
 }
}

void init_fair(size_t max_len)
{
 const UI N = (max_len + 1) / 2 + 1;
 REP(i_, N) {
  int i = i_ + 1; // 1 -> (max_len + 1) / 2
  if(i < 3) continue;
  if(i & 1) { // odd
   // 1xx0xx1 x can have 1 at most 6 positions
   //   1.0.1
   //   1.1.0.1.1
   //   1.1.1.0.1.1.1
   //   1.1.1.1.0.1.1.1.1
   // 1xx1xx1 x can have 1 at most 6 positions
   //   1.1.1
   //   1.1.1.1.1
   //   1.1.1.1.1.1.1
   //   1.1.1.1.1.1.1.1.1
   // 1xx2xx1 x can have 1 at most 2 positions
   //   1.2.1
   //   1.1.2.1.1
   // 2.2
   // 2.1.2

   const int X = (i - 1) / 2;
   // 2.2
   mygenerate(2*i-1, { {0,2}, {2*X,2} });
   // 1.0.1
   mygenerate(2*i-1, { {0,1}, {X,0}, {2*X,1} });
   // 1.1.1
   mygenerate(2*i-1, { {0,1}, {X,1}, {2*X,1} });
   // 1.2.1
   mygenerate(2*i-1, { {0,1}, {X,2}, {2*X,1} });
   // 2.1.2
   mygenerate(2*i-1, { {0,2}, {X,1}, {2*X,2} });

   for(auto ii : IR(1, X)) {
   // 1.1.0.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {X,0}, {2*X-ii,1}, {2*X,1} });
   // 1.1.1.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {X,1}, {2*X-ii,1}, {2*X,1} });
   // 1.1.2.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {X,2}, {2*X-ii,1}, {2*X,1} });
   }

   for(auto ii : IR(1, X)) {
    for(auto jj : IR(ii+1, X)) {
   // 1.1.1.0.1.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {jj,1}, {X,0}, {2*X-jj,1}, {2*X-ii,1}, {2*X,1} });
   // 1.1.1.1.1.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {jj,1}, {X,1}, {2*X-jj,1}, {2*X-ii,1}, {2*X,1} });
    }
   }

   for(auto ii : IR(1, X)) {
    for(auto jj : IR(ii+1, X)) {
     for(auto kk : IR(jj+1, X)) {
   // 1.1.1.1.0.1.1.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {jj,1}, {kk,1}, {X,0}, {2*X-kk,1}, {2*X-jj,1}, {2*X-ii,1}, {2*X,1} });
   // 1.1.1.1.1.1.1.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {jj,1}, {kk,1}, {X,1}, {2*X-kk,1}, {2*X-jj,1}, {2*X-ii,1}, {2*X,1} });
     }
    }
   }

  } else { // even
   const int X = i  / 2;

   // 1xxxx1 x can have 1 at most 6 positions
   //   1..1
   //   1.1..1.1
   //   1.1.1..1.1.1
   //   1.1.1.1..1.1.1.1
   // 2.2

   // 1..1
   mygenerate(2*i-1, { {0,1}, {2*X-1,1} });
   // 2.2
   mygenerate(2*i-1, { {0,2}, {2*X-1,2} });

   for(auto ii : IR(1, X)) {
   // 1.1..1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {2*X-ii-1,1}, {2*X-1,1} });
   }

   for(auto ii : IR(1, X)) {
    for(auto jj : IR(ii+1, X)) {
   // 1.1.1..1.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {jj,1}, {2*X-jj-1,1}, {2*X-ii-1,1}, {2*X-1,1} });
    }
   }

   for(auto ii : IR(1, X)) {
    for(auto jj : IR(ii+1, X)) {
     for(auto kk : IR(jj+1, X)) {
   // 1.1.1.1..1.1.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {jj,1}, {kk,1}, {2*X-kk-1,1}, {2*X-jj-1,1}, {2*X-ii-1,1}, {2*X-1,1} });
     }
    }
   }

  }
 }
 sort(RNG(fair), numeric_less());
#if 0
 for(auto & val : fair) {
  cout << val << endl;
 }
#endif
}

UI solve(const pair<string, string> &iv)
{
 auto it1 = lower_bound(RNG(fair), iv.first, numeric_less());
 auto it2 = lower_bound(RNG(fair), iv.second, numeric_less());
 UI result = it2 - it1;
 if(it2 != fair.end() && *it2 == iv.second) ++result;
 return result;
}

int main(void)
{
 ios_base::sync_with_stdio(false);

 UI cases; cin >> cases;
 size_t max_len = 0;
 vector<pair<string,string>> iv(cases);
 for(auto & val : iv) {
  cin >> val.first >> val.second;
  max_len = max({max_len, val.first.size(), val.second.size()});
 }
 init_fair(max_len);
 for(auto & val : iv) {
  cout << "Case #" << &val - iv.data() + 1 << ": " << solve(val) << endl;
 }

 return 0;
}

で、どこでミスったかというと

 sort(RNG(fair), numeric_less());
#if 0
 for(auto & val : fair) {
  cout << val << endl;
 }
#endif

が、こうなってました。

#if 0
 sort(RNG(fair), numeric_less());
 for(auto & val : fair) {
  cout << val << endl;
 }
#endif

ああああああああ。まぁ Qualification Round なので通ったからいいんですが。不用意な修正には気をつけろってことですね。

Problem D. Treasure

lexicographically smallest とか言われているのでこりゃ探索するしかないなってんで、どの chest を開いたかによって鍵の数も決まるので探索済みだったら枝刈りする形のコードが↓。これでも small input は通ります。あ、#include <boost/dynamic_bitset.hpp> の追加が必要です。

deque<int> solve(const vector<int> &required, const vector<vector<int>> &got, set<dynamic_bitset<>> &visited, dynamic_bitset<>& open, vector<int> & keys)
{
 if(visited.count(open)) return {};
 if(open.count() == required.size()) return { -1 };
//cerr << "OPENED_COUNT: " << open.count() << endl;
 REP(i, required.size()) {
  if(!open[i] && keys[required[i]] > 0) {
//cerr << "OPEN: " << i << endl;
   open.set(i);
   --keys[required[i]];
   REP(j, got[i].size()) {
    ++keys[got[i][j]];
   }
   auto t = solve(required, got, visited, open, keys);
   if(t.size()) {
    if(t.back() == -1) {
     t.back() = i+1;
    } else t.push_front(i+1);
    return t;
   }
   REP(j, got[i].size()) {
    --keys[got[i][j]];
   }
   ++keys[required[i]];
   open.reset(i);
  }
 }
 visited.insert(open);
 return {};
}

int main(void)
{
 ios_base::sync_with_stdio(false);

 UI cases; cin >> cases;
 REP(casenum, cases) {
  UI K, N; cin >> K >> N;
  vector<int> keys(200);
  REP(i, K) { UI t; cin >> t; ++keys[t-1]; }
  vector<int> required(N);
  vector<vector<int>> got;
  REP(i, N) {
   cin >> required[i]; --required[i];
   UI t; cin >> t;
   vector<int> got_(t);
   for(auto &val : got_) {
    cin >> val; --val;
   }
   got.push_back(got_);
  }

  dynamic_bitset<> open(N);
  set<dynamic_bitset<>> visited;
  cout << "Case #" << casenum+1 << ":";
  auto result = solve(required, got, visited, open, keys);
  if(result.size()) {
   for(auto &val: result) {
    cout << ' ' << val;
   }
  } else {
   cout << " IMPOSSIBLE";
  }
  cout << endl;
 }

 return 0;
}

IMPOSSIBLE に対する時間がかかりすぎるので、鍵の数が足りない場合と相互(3つ以上含む)に持ち合っている場合を除外「しよう」としたのが↓コードです(※通りません)。@tanakh さん曰く

ということで、連結してないケース=相互に持ち合ってるケースのイメージなので方針としては間違って無かったようです。手書きメモ上ではグラフみたいなもの書いてたのに(時間もあるのに)適当実装してしまったのが駄目なところですね。

deque<int> solve(const vector<int> &required, const vector<vector<int>> &got, set<dynamic_bitset<>> &visited, dynamic_bitset<>& open, vector<int> & keys)
{
 if(visited.count(open)) return {};
 if(open.count() == required.size()) return { -1 };
//cerr << "OPENED_COUNT: " << open.count() << endl;
 REP(i, required.size()) {
  if(!open[i] && keys[required[i]] > 0) {
//cerr << "OPEN: " << i << endl;
   open.set(i);
   --keys[required[i]];
   REP(j, got[i].size()) {
    ++keys[got[i][j]];
   }
   auto t = solve(required, got, visited, open, keys);
   if(t.size()) {
    if(t.back() == -1) {
     t.back() = i+1;
    } else t.push_front(i+1);
    return t;
   }
   REP(j, got[i].size()) {
    --keys[got[i][j]];
   }
   ++keys[required[i]];
   open.reset(i);
  }
 }
 visited.insert(open);
 return {};
}

bool sanity_check(const vector<int> &required, const vector<vector<int>> &got, const vector<int> &keys_orig)
{
 vector<int> req(200);
 REP(i, required.size()) {
  ++req[required[i]];
 }
 bool flag = false;
 REP(i, required.size()) {
  vector<int> keys(keys_orig);
  REP(j, required.size()) {
   if(i != j) {
    REP(k, got[j].size()) {
     ++keys[got[j][k]];
    }
   }
  }
  bool flag_one = true;
  REP(j, keys.size()) {
   if(req[j] > keys[j]) {
    flag_one = false;
    break;
   }
  }
  if(flag_one) {
   flag = true;
   break;
  }
 }
 return flag;
}

bool sanity_check2(const vector<int> &required, const vector<vector<int>> &got, const vector<int> &keys)
{
 stack<int> q;
 dynamic_bitset<> pushed(200);
 dynamic_bitset<> open(required.size());
 REP(i, keys.size()) { if(keys[i] && !pushed[i]) { pushed.set(i); q.push(i); } }
 while(!q.empty()) {
  int n = q.top(); q.pop();
  REP(i, required.size()) {
   if(required[i] == n && !open[i]) {
    open.set(i);
    for(auto j : got[i]) {
     if(!pushed[j]) { pushed.set(j); q.push(j); }
    }
   }
  }
 }
 return open.count() == required.size();
}

int main(void)
{
 ios_base::sync_with_stdio(false);

 UI cases; cin >> cases;
 REP(casenum, cases) {
  UI K, N; cin >> K >> N;
  vector<int> keys(200);
  REP(i, K) { UI t; cin >> t; ++keys[t-1]; }
  vector<int> required(N);
  vector<vector<int>> got;
  REP(i, N) {
   cin >> required[i]; --required[i];
   UI t; cin >> t;
   vector<int> got_(t);
   for(auto &val : got_) {
    cin >> val; --val;
   }
   got.push_back(got_);
  }

  dynamic_bitset<> open(N);
  set<dynamic_bitset<>> visited;
  cout << "Case #" << casenum+1 << ":";
  if(sanity_check(required, got, keys) && sanity_check2(required, got, keys)) {
   auto result = solve(required, got, visited, open, keys);
   if(result.size()) {
    for(auto &val: result) {
     cout << ' ' << val;
    }
   } else {
    cout << " IMPOSSIBLE";
   }
  } else {
   cout << " IMPOSSIBLE";
  }
  cout << endl;
 }

 return 0;
}

総評

C-Large はせっかく合ってたのに感はありますが通過できればいいんです、うん。とにかく楽しかったです。Round 1A も頑張るぞ。