// by GPT 5.2 #include using namespace std; // ---------------- RNG (fast) ---------------- struct XorShift64 { uint64_t x = 88172645463325252ull; inline uint64_t next() { x ^= x << 7; x ^= x >> 9; return x; } inline int nextInt(int n) { return (int)(next() % (uint64_t)n); } inline double nextDouble() { return (next() >> 11) * (1.0 / (1ull << 53)); } } rng; // ---------------- utilities ---------------- static inline int popcount10(int m) { return __builtin_popcount((unsigned)m); } struct Cand { array d; int mask; // 10-bit string s; }; // (h,b) categories for length 5 without repeats. // We'll map (h,b) to id via table. static int catId[6][6]; // -1 if invalid static vector> idToHB; static void initCats() { for(int h=0; h<=5; h++) for(int b=0; b<=5; b++) catId[h][b] = -1; idToHB.clear(); for(int h=0; h<=5; h++){ for(int b=0; b<=5; b++){ if(h==5 && b!=0) continue; if(h+b>5) continue; // b can be 0..5-h, that's ok catId[h][b] = (int)idToHB.size(); idToHB.push_back({h,b}); } } } // score between candidate c and query q (both length 5, distinct digits) static inline pair scoreHB(const Cand& c, const array& qd, int qmask){ int h=0; for(int i=0;i<5;i++) if(c.d[i]==qd[i]) h++; int common = popcount10(c.mask & qmask); int b = common - h; return {h,b}; } static inline int scoreId(const Cand& c, const array& qd, int qmask){ auto [h,b] = scoreHB(c, qd, qmask); return catId[h][b]; } // ---------------- build all 30240 candidates ---------------- static vector buildAll() { vector all; all.reserve(30240); array a; iota(a.begin(), a.end(), 0); // generate all 5-permutations of digits 0..9 // We'll do nested loops (fast, simple). for(int d0=0; d0<10; d0++){ for(int d1=0; d1<10; d1++) if(d1!=d0){ for(int d2=0; d2<10; d2++) if(d2!=d0 && d2!=d1){ for(int d3=0; d3<10; d3++) if(d3!=d0 && d3!=d1 && d3!=d2){ for(int d4=0; d4<10; d4++) if(d4!=d0 && d4!=d1 && d4!=d2 && d4!=d3){ Cand c; c.d = {d0,d1,d2,d3,d4}; c.mask = (1<> ask(const string& q){ cout << q << "\n"; cout.flush(); vector> hb(30); for(int i=0;i<30;i++){ if(!(cin >> hb[i].first >> hb[i].second)){ // EOF / error exit(0); } } if(hb[0].first==-1 && hb[0].second==-1){ // invalid interaction -> already judged wrong exit(0); } return hb; } // ---------------- SA solver ---------------- struct Solver { const vector& all; int Tq; // number of probe queries int K; // number of already-found secrets int N; // remaining secrets to find = 30-K vector> qd; vector qmask; // targetCounts[q][cat] = how many remaining secrets produce this (h,b) for query q vector> targetCounts; // Precomputed score id for each candidate and each query: sc[candIndex][q] vector> sc; // SA state: selected indices (in all), and inSel marker vector sel; vector inSel; // currentCounts[q][cat] vector> curCounts; int curScore = 0; Solver(const vector& all_, int Tq_) : all(all_), Tq(Tq_) {} static inline int l1diff(const vector& a, const vector& b){ int s=0; for(size_t i=0;i(Tq)); for(size_t i=0;i>>& rawHB, int foundK){ // rawHB[t] is the 30 pairs returned (sorted), AFTER asking probe t // We will compute target counts for the remaining N=30-foundK secrets: // For each t, remove (5,0) pairs which correspond to already-found secrets (persistent) K = foundK; N = 30 - K; int C = (int)idToHB.size(); targetCounts.assign(Tq, vector(C, 0)); for(int t=0;t cntAll(C, 0); for(auto [h,b] : rawHB[t]){ int id = catId[h][b]; if(id<0){ // should never happen exit(0); } cntAll[id]++; } // Remove persistent (5,0) caused by already found secrets int id50 = catId[5][0]; if(cntAll[id50] < K){ // This can happen if we mis-tracked foundK; be safe. // We'll clamp. cntAll[id50] = K; } cntAll[id50] -= K; // Now cntAll describes remaining secrets for this query. targetCounts[t] = std::move(cntAll); } } int computeScoreFromCounts(const vector>& counts){ int s=0; for(int t=0;t(C, 0)); for(int idx : sel) applyAdd(idx); curScore = computeScoreFromCounts(curCounts); } // Return true if found exact (score==0) within time iterations bool solveSA(double timeLimitSec){ using Clock = chrono::high_resolution_clock; auto st = Clock::now(); const int M = (int)all.size(); const int C = (int)idToHB.size(); int bestScore = INT_MAX; vector bestSel; // Multi-start SA int restarts = 0; while(true){ initRandomState(); if(curScore < bestScore){ bestScore = curScore; bestSel = sel; } // SA parameters double T0 = 2.0; double T1 = 0.02; // iterations in this restart for(int iter=0; iter<400000; iter++){ auto now = Clock::now(); double elapsed = chrono::duration(now - st).count(); if(elapsed > timeLimitSec) break; if(curScore==0) return true; double prog = elapsed / timeLimitSec; double Temp = T0 * pow(T1/T0, prog); // neighbor: swap one in sel with one out int pos = rng.nextInt(N); int outIdx = sel[pos]; int inIdx; for(;;){ inIdx = rng.nextInt(M); if(!inSel[inIdx]) break; } // delta update: remove outIdx, add inIdx // We'll compute delta score by applying and measuring change in L1 fast. int delta = 0; for(int t=0;t(now - st).count(); if(elapsed > timeLimitSec) break; restarts++; // continue restart } // If failed, keep best found (not exact). sel = bestSel; curScore = bestScore; return (curScore==0); } vector getSolutionIndices() const { return sel; } }; // ---------------- main strategy ---------------- int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); initCats(); auto all = buildAll(); // --- 20 fixed probe queries (deterministic, distinct digits) --- // We pick permutations produced by a simple rng seeded constant. // (Any fixed set works; these are chosen to have good variety.) const int PROBES = 20; vector probeStr; { XorShift64 rr; rr.x = 1234567891234567ull; // generate random 5-permutations without repetition; avoid duplicates unordered_set used; while((int)probeStr.size() < PROBES){ array d; iota(d.begin(), d.end(), 0); // shuffle for(int i=9;i>0;i--){ int j = rr.nextInt(i+1); swap(d[i], d[j]); } string s; for(int i=0;i<5;i++) s.push_back(char('0'+d[i])); if(used.insert(s).second) probeStr.push_back(s); } } // Track found secrets (by accidental exact hits during probes) vector found; unordered_set foundSet; // Record raw HB responses for each probe (always read 30 lines) vector>> rawHB(PROBES); int solvedK = 0; // number of already-found secrets (persistent (5,0) count) for(int t=0;t solvedK){ // Add this as found (may happen multiple at once? cannot, because only one secret equals this query) if(!foundSet.count(probeStr[t])){ found.push_back(probeStr[t]); foundSet.insert(probeStr[t]); } solvedK = cnt50; } // If already solved all, we can exit immediately. if(hb[0].first==5 && hb[0].second==0){ return 0; } } // Prepare solver with remaining constraints Solver solver(all, PROBES); solver.qd.resize(PROBES); solver.qmask.resize(PROBES); for(int t=0;t qd; int m=0; for(int i=0;i<5;i++){ int v = probeStr[t][i]-'0'; qd[i]=v; m |= 1< answerList; answerList.reserve(30); // Add already found (during probes) for(auto &s : found) answerList.push_back(s); // Add solved by SA (remaining candidates) auto idxs = solver.getSolutionIndices(); for(int idx : idxs){ answerList.push_back(all[idx].s); } // Deduplicate just in case (should not happen) sort(answerList.begin(), answerList.end()); answerList.erase(unique(answerList.begin(), answerList.end()), answerList.end()); // If SA failed or dedup removed something, fallback: // (Worst-case: just try all candidates sequentially - too many, but we keep something reasonable.) // Here we do a small safety net: if not 30, pad with probe strings (harmless). while((int)answerList.size() < 30){ // add more random unique strings string s = probeStr[rng.nextInt(PROBES)]; if(!binary_search(answerList.begin(), answerList.end(), s)){ answerList.insert(lower_bound(answerList.begin(), answerList.end(), s), s); } else { // random from all string t = all[rng.nextInt((int)all.size())].s; if(!binary_search(answerList.begin(), answerList.end(), t)){ answerList.insert(lower_bound(answerList.begin(), answerList.end(), t), t); } } } if((int)answerList.size() > 30) answerList.resize(30); // Now, ask each candidate in answerList to "lock" them to (5,0). // We may re-ask ones already asked in probes; it's okay (it just stays (5,0) if already solved), // but to reduce noise, we can skip those in probeStr set. unordered_set asked; for(auto &s: probeStr) asked.insert(s); // We must eventually reach hb[0]==(5,0) which means all 30 have been solved :contentReference[oaicite:6]{index=6} for(auto &s : answerList){ if(asked.count(s)) continue; // already asked during probe auto hb = ask(s); if(hb[0].first==5 && hb[0].second==0){ return 0; } } // If still not finished (due to SA failure), brute-force a bit more (bounded). // This is just a safety net; good runs should finish above. for(int tries=0; tries<2000; tries++){ string s = all[rng.nextInt((int)all.size())].s; if(asked.count(s)) continue; asked.insert(s); auto hb = ask(s); if(hb[0].first==5 && hb[0].second==0) return 0; } return 0; }