#include #include #include #include #include #include #include #include #include #include using namespace std; // 文字列とそのビットマスク表現(高速化のため) struct Candidate { string s; int mask; // 各数字が使われているかのビットマスク Candidate(string str) : s(str) { mask = 0; for (char c : str) { mask |= (1 << (c - '0')); } } }; // HitとBlowを計算する関数 // __builtin_popcount はGCC/Clang拡張でビットカウントを高速に行う pair calc_hb(const Candidate& a, const Candidate& b) { int hits = 0; for (int i = 0; i < 5; ++i) { if (a.s[i] == b.s[i]) hits++; } // Blow = (共通する数字の数) - Hit int common = __builtin_popcount(a.mask & b.mask); return {hits, common - hits}; } int main() { // 高速化 std::ios::sync_with_stdio(false); std::cin.tie(NULL); // 1. 全候補の生成 // 資料における「油田の配置全体の集合 X」 に相当 // 30,240通りしかないので全列挙可能 vector all_candidates; auto generate_all = [&]() { vector res; vector used(10, 0); auto dfs = [&](auto&& self, string current) -> void { if (current.size() == 5) { res.emplace_back(current); return; } for (int i = 0; i <= 9; ++i) { if (!used[i]) { used[i] = 1; self(self, current + to_string(i)); used[i] = 0; } } }; dfs(dfs, ""); return res; }; all_candidates = generate_all(); // 現在の有効な候補集合(インデックスで管理) // 初期状態では、真の配置 x である確率は一様 [cite: 154] vector active_candidates(all_candidates.size()); iota(active_candidates.begin(), active_candidates.end(), 0); // 既に特定された正解のインデックス集合 set confirmed; mt19937 rng(chrono::steady_clock::now().time_since_epoch().count()); // 30個すべて見つけるまでループ while (true) { int remaining_targets = 30 - confirmed.size(); // 残りの候補数が、未発見の正解数と一致したら、それらが正解 if (active_candidates.size() == remaining_targets) { for (int idx : active_candidates) { cout << all_candidates[idx].s << endl; // ダミー入力読み飛ばし string d; for(int k=0; k<30; ++k) cin >> d >> d; } return 0; } // 2. 相互情報量に基づく質問の選択 [cite: 224, 235] // 全候補を評価するのは重いため、ランダムにサンプリングして評価 [cite: 244] int best_query_idx = -1; if (active_candidates.size() <= remaining_targets) { best_query_idx = active_candidates[0]; } else { // 評価する候補数 int sample_size = min((int)active_candidates.size(), 50); vector probes; sample(active_candidates.begin(), active_candidates.end(), back_inserter(probes), sample_size, rng); double max_entropy = -1.0; for (int q_idx : probes) { // シミュレーション: この質問qをしたとき、候補がどのような分布になるか // 資料の「占い結果の確率分布」[cite: 218] を計算 int counts[6][6] = {0}; for (int c_idx : active_candidates) { pair hb = calc_hb(all_candidates[c_idx], all_candidates[q_idx]); counts[hb.first][hb.second]++; } // エントロピー H(Y) の計算 // バラつきが大きいほどエントロピーは高くなる double entropy = 0; double total = active_candidates.size(); for(int h=0; h<6; ++h) { for(int b=0; b<6; ++b) { if (counts[h][b] > 0) { double p = counts[h][b] / total; entropy -= p * log2(p); } } } if (entropy > max_entropy) { max_entropy = entropy; best_query_idx = q_idx; } } } // 質問出力 cout << all_candidates[best_query_idx].s << endl; // 3. 結果の受け取りと候補の絞り込み(事後確率の更新)[cite: 178] // 実際の結果Rを受け取る int response_counts[6][6] = {0}; bool all_correct = true; for (int i = 0; i < 30; ++i) { int h, b; cin >> h >> b; response_counts[h][b]++; if (h != 5 || b != 0) all_correct = false; } if (all_correct) return 0; // 全て(5,0)なら終了 // ルール: 既知の正解に対しては (5, 0) が返ってくる // これを除外して、未知の正解に対する分布を得る int expected_5_0 = confirmed.size(); int actual_5_0 = response_counts[5][0]; if (actual_5_0 > expected_5_0) { // 新しい正解が見つかった! confirmed.insert(best_query_idx); response_counts[5][0] -= actual_5_0; } else { response_counts[5][0] -= expected_5_0; } // 尤度計算とフィルタリング [cite: 175, 250] // 返ってきた分布 `response_counts` と矛盾する候補を削除する vector next_candidates; next_candidates.reserve(active_candidates.size()); for (int c_idx : active_candidates) { if (confirmed.count(c_idx)) continue; // 候補 c が正解だと仮定したときの Hit/Blow pair hb = calc_hb(all_candidates[c_idx], all_candidates[best_query_idx]); // その結果が、実際の返答リストに含まれていれば、尤度 > 0 if (response_counts[hb.first][hb.second] > 0) { next_candidates.push_back(c_idx); } } active_candidates = next_candidates; } return 0; }