#include using namespace std; /* 30個並列ごちゃ混ぜ Hit&Blow - 候補全30240を列挙 - 各質問の返答((H,B)ヒストグラム)を制約として保持 - IPF(Iterative Proportional Fitting)で候補の重みwを更新し、 wが最大の候補を質問する(当たりやすいものから潰す) - 5秒制限対策:IPF反復回数/使用する過去質問数を調整、時間が迫れば軽量化 */ static constexpr int L = 5; static constexpr int DIG = 10; static constexpr int N = 30240; static constexpr int CODES = 36; // code = hit*6 + blow (0..35) // ===== 調整パラメータ ===== static constexpr double TIME_LIMIT_SEC = 4.85; // 5sに対して少し余裕 static constexpr int IPF_ITERS_BASE = 10; // IPF反復回数(増やすほど重いが当たりやすくなることが多い) static constexpr int IPF_USE_LAST = 16; // IPFに使う過去質問数(末尾K個) static constexpr bool USE_FIXED_PROBES = true; // 初期探索を固定するか // ========================== struct Cand { array d; uint16_t mask; // 10-bit }; struct QueryInfo { int q_idx; array rem; // (5,0)以外の個数(未発見分) vector code_to; // size N: code_to[i] = score(q, cand[i]) as code }; static inline int encode_code(int hit, int blow) { return hit * 6 + blow; } int main() { // ios::sync_with_stdio(false); // cin.tie(nullptr); // 時間計測 const auto t_start = chrono::steady_clock::now(); auto elapsed_sec = [&]() -> double { auto now = chrono::steady_clock::now(); return chrono::duration(now - t_start).count(); }; // popcount(10bit)テーブル static uint8_t pop10[1 << 10]; for (int m = 0; m < (1 << 10); m++) pop10[m] = (uint8_t)__builtin_popcount((unsigned)m); // 候補30240生成 + 文字列->index簡易マップ vector cand; cand.reserve(N); unordered_map idx_of; // key = a*10000 + b*1000 + c*100 + d*10 + e idx_of.reserve(N * 2); for (int a = 0; a < 10; a++) for (int b = 0; b < 10; b++) if (b != a) for (int c = 0; c < 10; c++) if (c != a && c != b) for (int d = 0; d < 10; d++) if (d != a && d != b && d != c) for (int e = 0; e < 10; e++) if (e != a && e != b && e != c && e != d) { Cand x; x.d = {(uint8_t)a,(uint8_t)b,(uint8_t)c,(uint8_t)d,(uint8_t)e}; x.mask = (uint16_t)((1< int { int key = 0; for (int i = 0; i < L; i++) key = key * 10 + (s[i] - '0'); auto it = idx_of.find(key); if (it == idx_of.end()) return -1; return it->second; }; auto print_query = [&](int q_idx) { const auto &d = cand[q_idx].d; for (int i = 0; i < L; i++) cout << char('0' + d[i]); cout << "\n" << flush; // flush必須 }; auto read_response = [&]() -> vector> { vector> hb(30); for (int i = 0; i < 30; i++) { if (!(cin >> hb[i].first >> hb[i].second)) { // EOF等 exit(0); } } return hb; }; auto compute_code_to = [&](int q_idx) -> vector { vector code_to(N); const auto &qd = cand[q_idx].d; uint16_t qmask = cand[q_idx].mask; for (int i = 0; i < N; i++) { int hit = 0; const auto &sd = cand[i].d; hit += (sd[0] == qd[0]); hit += (sd[1] == qd[1]); hit += (sd[2] == qd[2]); hit += (sd[3] == qd[3]); hit += (sd[4] == qd[4]); int common = pop10[cand[i].mask & qmask]; int blow = common - hit; code_to[i] = (uint8_t)encode_code(hit, blow); } return code_to; }; // 状態 vector alive(N, 1); vector asked(N, 0); vector alive_list; alive_list.reserve(N); for (int i = 0; i < N; i++) alive_list.push_back(i); vector hist; hist.reserve(80); int found_cnt = 0; // (5,0)の個数 // 初期探索(固定2手) vector probe_indices; if (USE_FIXED_PROBES) { int p1 = idx_from_string("01234"); int p2 = idx_from_string("56789"); if (p1 >= 0) probe_indices.push_back(p1); if (p2 >= 0) probe_indices.push_back(p2); } // alive_list再構築(prune含む) auto prune_all = [&]() { vector next; next.reserve(alive_list.size()); for (int idx : alive_list) { if (!alive[idx] || asked[idx]) { alive[idx] = 0; continue; } bool ok = true; for (const auto &q : hist) { if (q.rem[q.code_to[idx]] == 0) { ok = false; break; } } if (!ok) { alive[idx] = 0; } else { next.push_back(idx); } } alive_list.swap(next); }; // メインループ int step = 0; while (true) { // 打ち切り防止:候補が尽きたら(通常起きないが)終了 prune_all(); if (alive_list.empty()) { // 最悪:未質問のものを適当に投げ続ける(ここに来ない想定) for (int i = 0; i < N; i++) if (!asked[i]) { print_query(i); auto hb = read_response(); if (hb[0].first == -1) return 0; if (hb[0].first == 5 && hb[0].second == 0) return 0; } return 0; } int q_idx = -1; // 時間が厳しいなら軽量化(末尾から適当に) double t = elapsed_sec(); if (t > TIME_LIMIT_SEC) { q_idx = alive_list[0]; } else if (step < (int)probe_indices.size()) { // 初期探索 q_idx = probe_indices[step]; if (q_idx < 0 || asked[q_idx] || !alive[q_idx]) { q_idx = alive_list[0]; } } else { // IPFで重み計算 int remaining = 30 - found_cnt; // 使う過去質問を末尾K個に制限 int K = (int)hist.size(); K = min(K, IPF_USE_LAST); int start_idx = (int)hist.size() - K; // 残り時間で反復回数を調整 int iters = IPF_ITERS_BASE; double left = TIME_LIMIT_SEC - t; if (left < 0.20) iters = min(iters, 2); else if (left < 0.40) iters = min(iters, 4); static vector w; w.assign(N, 0.0f); // 初期重み(aliveのみ) float scale0 = (alive_list.empty() ? 0.0f : (float)remaining / (float)alive_list.size()); for (int idx : alive_list) w[idx] = scale0; array tot{}; array factor{}; for (int it = 0; it < iters; it++) { for (int qi = start_idx; qi < (int)hist.size(); qi++) { const auto &Q = hist[qi]; // tot初期化 tot.fill(0.0f); // 集計 for (int idx : alive_list) { tot[Q.code_to[idx]] += w[idx]; } // factor計算 for (int c = 0; c < CODES; c++) { if (tot[c] > 1e-20f) factor[c] = (float)Q.rem[c] / tot[c]; else factor[c] = 0.0f; } // 更新 for (int idx : alive_list) { w[idx] *= factor[Q.code_to[idx]]; } } // 正規化(sum w = remaining) double sumW = 0.0; for (int idx : alive_list) sumW += (double)w[idx]; if (sumW > 0) { float s = (float)((double)remaining / sumW); for (int idx : alive_list) w[idx] *= s; } } // 最大重みを選ぶ float best = -1.0f; int best_idx = alive_list[0]; for (int idx : alive_list) { if (w[idx] > best) { best = w[idx]; best_idx = idx; } } q_idx = best_idx; } // 質問 print_query(q_idx); auto hb = read_response(); // 不正/終了チェック if (hb[0].first == -1) return 0; if (hb[0].first == 5 && hb[0].second == 0) return 0; // found_cnt更新((5,0)の数) int new_found = 0; for (auto &p : hb) if (p.first == 5 && p.second == 0) new_found++; // もし今回で新しく秘密を当てた(= q_idx が秘密の一つ)なら、 // 過去の全クエリのremからこの秘密分を差し引く if (new_found > found_cnt) { for (auto &Q : hist) { int c = Q.code_to[q_idx]; if (Q.rem[c] > 0) Q.rem[c]--; } } found_cnt = new_found; // q_idxは二度と使わない(当たりでも外れでも、秘密は重複しないので) asked[q_idx] = 1; alive[q_idx] = 0; // 今回のクエリを制約として追加 QueryInfo qi; qi.q_idx = q_idx; qi.rem.fill(0); // (5,0) は「既発見扱い」なので rem に入れない for (auto &p : hb) { if (p.first == 5 && p.second == 0) continue; int code = encode_code(p.first, p.second); qi.rem[code]++; } qi.code_to = compute_code_to(q_idx); hist.push_back(std::move(qi)); step++; } }