#include using namespace std; // ------------------------------ // 30-parallel Hit&Blow solver // Strategy: pick the (currently) most probable remaining code. // Probability is estimated by maximum-entropy raking (iterative proportional fitting) // under the per-query (H,B) histogram constraints. // ------------------------------ struct Code { array d; uint16_t mask; array s; // null-terminated }; static int HB_ID[6][6]; static int ID_50; static constexpr int K = 21; // number of (hit,blow) types static inline int popcnt10(uint16_t x){ return __builtin_popcount((unsigned)x); } int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); // Build mapping (hit,blow) -> 0..20 for(int h=0;h<=5;h++) for(int b=0;b<=5;b++) HB_ID[h][b] = -1; int kk=0; for(int h=0;h<=5;h++){ for(int b=0;b<=5-h;b++) HB_ID[h][b] = kk++; } ID_50 = HB_ID[5][0]; // Generate all 10P5 codes (30240) vector all; all.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){ Code x; x.d = {(uint8_t)a,(uint8_t)b,(uint8_t)c,(uint8_t)d,(uint8_t)e}; x.mask = (uint16_t)((1u<int{ int hits = 0; for(int k=0;k<5;k++) hits += (all[i].d[k] == all[j].d[k]); int common = popcnt10((uint16_t)(all[i].mask & all[j].mask)); int blow = common - hits; return HB_ID[hits][blow]; }; // ------------------------------ // Solver state // ------------------------------ vector asked(N,false); // asked at least once (found or not) vector alive(N,true); // still possible to be in the remaining unknown set vector alive_list(N); iota(alive_list.begin(), alive_list.end(), 0); // History vector queries; // query indices vector> cnt; // residual histograms for current unknown set vector> cats; // cats[q][i] = category of HB(query[q], i) vector zeroMask; // categories with count==0 for each query vector w(N, 1.0); // weights ~ existence probability (expected count) int found_count = 0; // how many hidden strings are already identified // Time control (computation only; I/O cost dominates on the judge side) constexpr long long TIME_LIMIT_MS = 4900; // leave some margin under 5s constexpr int BASE_SWEEPS = 3; // accuracy knob (increase if you have time) auto t0 = chrono::steady_clock::now(); auto elapsed_ms = [&]()->long long{ return chrono::duration_cast(chrono::steady_clock::now() - t0).count(); }; auto normalize = [&](int n_rem){ double tot = 0.0; for(int i: alive_list) tot += w[i]; if(tot <= 0.0){ for(int i: alive_list) w[i] = 1.0; tot = (double)alive_list.size(); } double sc = (double)n_rem / tot; for(int i: alive_list) w[i] *= sc; }; auto prune_with_mask = [&](int qid, uint32_t maskBits){ // Remove i from alive_list if cats[qid][i] is in maskBits vector new_alive; new_alive.reserve(alive_list.size()); const auto &cat = cats[qid]; for(int i: alive_list){ if(!alive[i]) continue; int c = cat[i]; if(maskBits & (1u< 0.0) factor[r] = (double)cnt[q][r] / sum[r]; else factor[r] = (cnt[q][r] == 0 ? 1.0 : 0.0); } for(int i: alive_list) w[i] *= factor[cat[i]]; } } normalize(n_rem); }; // ------------------------------ // Main interaction loop // ------------------------------ while(true){ if(found_count >= 30) return 0; // just in case if(alive_list.empty()){ // Extremely unlikely unless something went wrong. // Fall back to "ask everything not asked". for(int i=0;i>h>>b; if(!cin) return 0; if(h==-1 && b==-1) bad=true; if(h==5 && b==0) c50++; } if(bad || c50==30) return 0; } return 0; } const int n_rem = 30 - found_count; int qidx = -1; if((int)alive_list.size() == n_rem){ // Only n_rem candidates are possible -> all must exist with probability 1. qidx = alive_list[0]; }else{ long long rem = TIME_LIMIT_MS - elapsed_ms(); int sweeps = BASE_SWEEPS; if(rem < 200) sweeps = 0; else if(rem < 400) sweeps = min(sweeps, 1); else if(rem < 800) sweeps = min(sweeps, 2); if(sweeps > 0) rake(n_rem, sweeps); else normalize(n_rem); qidx = alive_list[0]; double best = w[qidx]; for(int i: alive_list){ if(w[i] > best){ best = w[i]; qidx = i; } } if(best <= 0.0){ // fallback: pick something to make progress qidx = alive_list[(uint64_t)elapsed_ms() % alive_list.size()]; } } // Ask qidx asked[qidx] = true; // (We mark alive=false now; it will be physically removed by prune right after we add this query.) alive[qidx] = false; w[qidx] = 0.0; cout.write(all[qidx].s.data(), 5); cout << '\n' << flush; // required // Read judge response (30 lines) array hist{}; hist.fill(0); bool bad = false; int c50 = 0; for(int k=0;k<30;k++){ int h,b; cin >> h >> b; if(!cin) return 0; if(h==-1 && b==-1) bad = true; if(!bad){ int id = HB_ID[h][b]; if(id >= 0) hist[id]++; if(h==5 && b==0) c50++; } } if(bad) return 0; if(c50 == 30) return 0; // all solved int prev_found = found_count; bool new_found = (c50 > prev_found); found_count = c50; // residual histogram for the currently unknown set (after this query) array resid = hist; resid[ID_50] -= found_count; // Store this query queries.push_back(qidx); cnt.push_back(resid); cats.emplace_back(N); int qid = (int)queries.size() - 1; for(int i=0;i