#include using namespace std; /* 30個並列・ソート済みHit&Blowの多重集合だけが返る問題に対し、 「残りの候補が各クエリの( h,b )ヒストグラム制約を満たすように重みを調整(IPF)」 →「最も重い候補を次にクエリ」 を繰り返す。 - flush必須 - 毎回30行読む - 5s制限なので、時間が厳しくなったらIPF sweep数を落とす */ static constexpr int L = 5; static constexpr int DIG = 10; int main() { // ios::sync_with_stdio(false); // cin.tie(nullptr); // ---- popcount (10bit) ---- array popc{}; for (int m = 0; m < (1 << DIG); m++) popc[m] = __builtin_popcount((unsigned)m); // ---- (h,b) -> id (20種類; (4,1)は除外) ---- int id_table[6][6]; for (auto &row : id_table) for (int &x : row) x = -1; vector> id2pair; for (int h = 0; h <= 5; h++) { for (int b = 0; b <= 5 - h; b++) { if (h == 4 && b == 1) continue; // 不可能 int id = (int)id2pair.size(); id2pair.push_back({h,b}); id_table[h][b] = id; } } const int S = (int)id2pair.size(); // 20 const int ID50 = id_table[5][0]; // ---- 全候補(30240=10P5)を列挙 ---- vector> cand_digits; vector cand_mask; vector cand_str; cand_digits.reserve(30240); cand_mask.reserve(30240); cand_str.reserve(30240); 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) { array dig = {(uint8_t)a,(uint8_t)b,(uint8_t)c,(uint8_t)d,(uint8_t)e}; uint16_t mask = (1u< asked(U, 0), found(U, 0), dead(U, 0); vector found_time(U, -1); vector found_list; found_list.reserve(30); vector q_idxs; // 各クエリとして出した候補のindex vector> obs_counts; // 観測した(20種)ヒストグラム(その時点の judge 仕様どおり) vector> resp_cols; // resp_cols[j][u] = candidate u の query j に対する response id // 重み(IPF) vector w(U, 1.0); auto start = chrono::steady_clock::now(); const long long TIME_LIMIT_MS = 4900; // 少し安全側 const double EPS = 1e-12; auto elapsed_ms = [&]() -> long long { auto now = chrono::steady_clock::now(); return chrono::duration_cast(now - start).count(); }; auto calc_resp_id = [&](int u, int qidx) -> uint8_t { int hit = 0; const auto &sd = cand_digits[u]; const auto &qd = cand_digits[qidx]; for (int i = 0; i < L; i++) hit += (sd[i] == qd[i]); int m = popc[(int)(cand_mask[u] & cand_mask[qidx])]; int b = m - hit; int id = id_table[hit][b]; if (id < 0) id = 0; // 念のため(基本来ない) return (uint8_t)id; }; auto add_query = [&](int qidx) -> bool { int qi = (int)q_idxs.size(); q_idxs.push_back(qidx); asked[qidx] = 1; // 出力 & flush cout << cand_str[qidx] << "\n" << flush; array cnt; cnt.fill(0); int h0 = -2, b0 = -2; for (int i = 0; i < 30; i++) { int h, b; if (!(cin >> h >> b)) { // 入力が途切れた(通常は起きない) exit(0); } if (i == 0) { h0 = h; b0 = b; } if (h == -1 && b == -1) { // 不正やり取り扱い exit(0); } int id = (0 <= h && h <= 5 && 0 <= b && b <= 5) ? id_table[h][b] : -1; if (0 <= id && id < 20) cnt[id]++; } obs_counts.push_back(cnt); // 全部当て終わったら (H0,B0)=(5,0) if (h0 == 5 && b0 == 0) return true; // 当たり判定: (5,0) の個数が「既知found数」より増えていれば新規ヒット int found_before = (int)found_list.size(); int new_hits = cnt[ID50] - found_before; if (new_hits > 0) { found[qidx] = 1; found_time[qidx] = qi; found_list.push_back(qidx); } else { // 今回出した文字列はセットに含まれないと確定 dead[qidx] = 1; } // resp_col を作る(全候補 u の、このクエリに対する応答id) vector col(U); for (int u = 0; u < U; u++) col[u] = calc_resp_id(u, qidx); resp_cols.push_back(std::move(col)); return false; }; // ---- 初手 ---- if (add_query(INIT_Q)) return 0; // ---- メインループ ---- while (true) { int K = (int)q_idxs.size(); int F = (int)found_list.size(); int R = 30 - F; if (R <= 0) return 0; // 時間がきつければ sweep を減らす(打ち切り) long long ms = elapsed_ms(); int sweeps = 3; if (ms > TIME_LIMIT_MS - 800) sweeps = 1; else if (ms > TIME_LIMIT_MS - 1800) sweeps = 2; // --- 調整済みヒストグラム(「残りR個」ぶん)を作る --- vector> adj = obs_counts; // copy for (int s : found_list) { int ft = found_time[s]; for (int j = 0; j < K; j++) { if (ft < j) { // クエリjの時点で既に見つかっていた -> (5,0) として出力されている adj[j][ID50]--; } else { // まだ見つかっていない時点のクエリ -> 本来の応答を引く uint8_t id = resp_cols[j][s]; adj[j][(int)id]--; } } } // --- 重みベクトルの準備 --- // found/dead は 0 にする double sumw = 0.0; for (int u = 0; u < U; u++) { if (found[u] || dead[u]) w[u] = 0.0; sumw += w[u]; } if (sumw <= 0.0) { // 何かで潰れていたら再初期化 sumw = 0.0; for (int u = 0; u < U; u++) { w[u] = (found[u] || dead[u]) ? 0.0 : 1.0; sumw += w[u]; } } // 合計Rに正規化 { double scale = (sumw > 0.0) ? ((double)R / sumw) : 1.0; for (int u = 0; u < U; u++) w[u] *= scale; } // --- IPF sweeps --- for (int it = 0; it < sweeps; it++) { for (int j = 0; j < K; j++) { // 期待値 E[t] を計算 double E[20]; for (int t = 0; t < 20; t++) E[t] = 0.0; const auto &col = resp_cols[j]; for (int u = 0; u < U; u++) { double wu = w[u]; if (wu == 0.0) continue; E[col[u]] += wu; } // ratio[t] = obs/expected double ratio[20]; for (int t = 0; t < 20; t++) { if (adj[j][t] <= 0) ratio[t] = 0.0; else { ratio[t] = (double)adj[j][t] / (E[t] + EPS); // 念のため過度な発散を抑える(ほぼ不要だが安全策) if (ratio[t] > 1e6) ratio[t] = 1e6; } } // 重み更新 for (int u = 0; u < U; u++) { double wu = w[u]; if (wu == 0.0) continue; w[u] = wu * ratio[col[u]]; } } // sweep後に再正規化(合計Rに戻す) sumw = 0.0; for (int u = 0; u < U; u++) sumw += w[u]; if (sumw <= 0.0) { // 潰れたら再初期化 sumw = 0.0; for (int u = 0; u < U; u++) { w[u] = (found[u] || dead[u]) ? 0.0 : 1.0; sumw += w[u]; } } double scale = (sumw > 0.0) ? ((double)R / sumw) : 1.0; for (int u = 0; u < U; u++) w[u] *= scale; } // --- 次に尋ねる:最も重い未質問候補 --- int best = -1; double bestw = -1.0; for (int u = 0; u < U; u++) { if (asked[u] || found[u] || dead[u]) continue; if (w[u] > bestw) { bestw = w[u]; best = u; } } if (best < 0) { // 念のため(通常は起きない) for (int u = 0; u < U; u++) { if (!asked[u] && !found[u] && !dead[u]) { best = u; break; } } if (best < 0) return 0; } if (add_query(best)) return 0; if ((int)found_list.size() >= 30) return 0; } }