// By GPT 5.2 Pro #include using namespace std; /* 方針(重要アイデア): - クエリ q の数字集合を Q (|Q|=5) とする。 - ある未解決の秘密文字列 S の数字集合を D (|D|=5) とする。 - D==Q のときに限り、S と q の共通数字数は 5 なので必ず h+b=5 になる。 (数字は相異・長さ 5 なので) - すでに当てた S は以後どんなクエリでも (5,0) が返る。 - したがって「h+b==5 の個数」から、クエリ集合 Q と同じ数字集合を持つ秘密文字列が何本あるか数えられる。 ただし、過去に当てた本数(= (5,0) の本数)ぶんは常に h+b==5 に混入するので差し引く。 */ struct Reply { array,30> hb{}; int solvedCount = 0; // (5,0) の数 bool allSolved = false; // hb[0]==(5,0) なら全て (5,0) bool invalid = false; // (-1,-1) が来たら不正 }; static int g_solvedCount = 0; // これまでに当てた本数 static unordered_set g_solvedStrings; // 当てた文字列集合 static unordered_set g_asked; // 既に投げたクエリ(無駄撃ち回避) static array g_solvedByMask{}; // 数字集合(mask)ごとの当てた本数 static mt19937 rng(1234567); static inline int maskFromString(const string& s){ int m=0; for(char c: s) m |= 1<<(c-'0'); return m; } static inline int hitCount(const string& a, const string& b){ int h=0; for(int i=0;i<5;i++) if(a[i]==b[i]) h++; return h; } // クエリを出して 30 行読む(インタラクティブ) static Reply ask(const string& q){ cout << q << "\n" << flush; Reply rep; for(int i=0;i<30;i++){ int h,b; if(!(cin >> h >> b)){ // EOF: ここまでで終了 exit(0); } rep.hb[i] = {h,b}; } if(rep.hb[0].first == -1 && rep.hb[0].second == -1){ rep.invalid = true; return rep; } int sc=0; for(auto &p: rep.hb) if(p.first==5 && p.second==0) sc++; rep.solvedCount = sc; rep.allSolved = (rep.hb[0].first==5 && rep.hb[0].second==0); return rep; } static void recordSolvedString(const string& s){ if(g_solvedStrings.insert(s).second){ int m = maskFromString(s); g_solvedByMask[m]++; } } // 「今回のクエリの数字集合」と一致する(未解決の)秘密文字列たちの hit 値 multiset を取り出す。 // 具体的には、h+b==5 のペア列から「以前に当てた本数(prevSolved)」だけ (5,0) を除去し、残りの h を返す。 // ※クエリと同じ数字集合のものだけが未解決で h+b==5 になる。既解決は常に (5,0) で混ざるので引く。 static vector extractHitsSameDigitSet(const Reply& rep, int prevSolved){ vector> sum5; sum5.reserve(30); for(auto &p: rep.hb){ if(p.first >= 0 && p.first + p.second == 5) sum5.push_back(p); } int remove = prevSolved; vector hits; hits.reserve(sum5.size()); for(auto &p: sum5){ if(p.first==5 && p.second==0 && remove>0){ remove--; }else{ hits.push_back(p.first); } } sort(hits.begin(), hits.end()); return hits; } static vector digitsFromMask(int mask){ vector d; for(int x=0;x<10;x++) if(mask&(1<& d){ string s; for(int x: d) s.push_back(char('0'+x)); return s; } static vector allPermutationsOfDigits(vector d){ sort(d.begin(), d.end()); vector perms; do{ string s; for(int x: d) s.push_back(char('0'+x)); perms.push_back(s); }while(next_permutation(d.begin(), d.end())); // 5! = 120 return perms; } struct MaskInfo { int cnt = 0; // この数字集合を持つ秘密文字列の本数 string scanQuery; // スキャンで投げたクエリ(昇順) vector scanHitsAll; // scanQuery に対する「この集合の文字列」全本数ぶんの hit multiset(サイズ cnt) }; // フォールバック: この数字集合の 120 順列を順に投げて remaining 本当てる static void bruteForceSolveMask(const MaskInfo& info, int mask, int remaining){ if(remaining<=0) return; auto digits = digitsFromMask(mask); auto perms = allPermutationsOfDigits(digits); for(const string& q: perms){ if(remaining<=0) return; if(g_solvedStrings.count(q)) continue; if(g_asked.count(q)) continue; // なるべく無駄を避ける int prevSolved = g_solvedCount; Reply rep = ask(q); g_asked.insert(q); if(rep.invalid) exit(0); if(rep.solvedCount > prevSolved){ recordSolvedString(q); remaining--; } g_solvedCount = rep.solvedCount; if(rep.allSolved) exit(0); } } // remaining==1 を「候補絞り込み(minimax)」で解く static void solveSingleInMask(const MaskInfo& info, int mask){ int remaining = info.cnt - g_solvedByMask[mask]; if(remaining!=1) return; auto digits = digitsFromMask(mask); auto perms = allPermutationsOfDigits(digits); // scanHitsAll から、すでにこの mask で解いた分を引いて「残り1本の scan hit」を得る vector scanHits = info.scanHitsAll; for(const string& s: g_solvedStrings){ if(maskFromString(s)==mask){ int h = hitCount(s, info.scanQuery); auto it = find(scanHits.begin(), scanHits.end(), h); if(it!=scanHits.end()) scanHits.erase(it); } } vector candidates; candidates.reserve(120); if(scanHits.size()==1){ int h0 = scanHits[0]; for(int i=0;i<(int)perms.size();i++){ if(g_solvedStrings.count(perms[i])) continue; if(hitCount(perms[i], info.scanQuery)==h0) candidates.push_back(i); } }else{ // 万一崩れたら全候補から for(int i=0;i<(int)perms.size();i++){ if(g_solvedStrings.count(perms[i])) continue; candidates.push_back(i); } } // 安全策 if(candidates.empty()){ bruteForceSolveMask(info, mask, 1); return; } // 追加クエリで絞る(最大 12 回程度で打ち切ってフォールバック) for(int step=0; step<12; step++){ if(g_solvedCount>=30) exit(0); if(candidates.size()==1){ string guess = perms[candidates[0]]; if(!g_asked.count(guess) && !g_solvedStrings.count(guess)){ int prevSolved = g_solvedCount; Reply rep = ask(guess); g_asked.insert(guess); if(rep.invalid) exit(0); if(rep.solvedCount > prevSolved){ recordSolvedString(guess); } g_solvedCount = rep.solvedCount; if(rep.allSolved) exit(0); } return; } // minimax(候補を最も割る質問を探す) int bestQ = -1; int bestMax = INT_MAX; for(int qi=0; qi<(int)perms.size(); qi++){ const string& q = perms[qi]; if(g_asked.count(q)) continue; int bucket[6]={0,0,0,0,0,0}; for(int ci: candidates){ int h = hitCount(perms[ci], q); bucket[h]++; } int mx=0; for(int k=0;k<6;k++) mx=max(mx, bucket[k]); if(mx < bestMax){ bestMax = mx; bestQ = qi; } } if(bestQ==-1){ // すべて聞いてしまった等: 先頭候補を投げる bestQ = candidates[0]; } string q = perms[bestQ]; int prevSolved = g_solvedCount; Reply rep = ask(q); g_asked.insert(q); if(rep.invalid) exit(0); // この mask のクエリなので、増えていればこの mask の解 if(rep.solvedCount > prevSolved){ recordSolvedString(q); } // hit の観測(この mask に残っている 1 本ぶん) vector hits = extractHitsSameDigitSet(rep, prevSolved); g_solvedCount = rep.solvedCount; if(rep.allSolved) exit(0); if(hits.size()!=1){ bruteForceSolveMask(info, mask, 1); return; } int obs = hits[0]; // obs==5 なら q が正解で解けた if(obs==5) return; vector nextCand; nextCand.reserve(candidates.size()); for(int ci: candidates){ if(hitCount(perms[ci], q)==obs) nextCand.push_back(ci); } candidates.swap(nextCand); if(candidates.empty()){ bruteForceSolveMask(info, mask, 1); return; } } // 打ち切りフォールバック bruteForceSolveMask(info, mask, 1); } // remaining==2 or 3 を「部分集合列挙→マルチセット一致」で解く template static void solveMultiInMask(const MaskInfo& info, int mask){ int remaining = info.cnt - g_solvedByMask[mask]; if(remaining!=R) return; auto digits = digitsFromMask(mask); auto perms = allPermutationsOfDigits(digits); // scanHitsAll から、すでにこの mask で解いた分を引いて「残りR本の scan hit」を得る vector scanHits = info.scanHitsAll; for(const string& s: g_solvedStrings){ if(maskFromString(s)==mask){ int h = hitCount(s, info.scanQuery); auto it = find(scanHits.begin(), scanHits.end(), h); if(it!=scanHits.end()) scanHits.erase(it); } } if((int)scanHits.size()!=R){ // 崩れていたら brute force bruteForceSolveMask(info, mask, remaining); return; } sort(scanHits.begin(), scanHits.end()); // 候補部分集合を作る(scanQuery に対する hit multiset が一致するものだけ) vector avail; avail.reserve(120); for(int i=0;i<(int)perms.size();i++){ if(g_solvedStrings.count(perms[i])) continue; avail.push_back(i); } struct Sub { array a; }; vector subs; auto sc = info.scanQuery; if constexpr (R==2){ for(int xi=0; xi<(int)avail.size(); xi++){ for(int yi=xi+1; yi<(int)avail.size(); yi++){ int i=avail[xi], j=avail[yi]; int h1=hitCount(perms[i], sc); int h2=hitCount(perms[j], sc); array hs{h1,h2}; sort(hs.begin(), hs.end()); if(hs[0]==scanHits[0] && hs[1]==scanHits[1]){ Sub s; s.a = {(uint8_t)i,(uint8_t)j}; subs.push_back(s); } } } } else { // R==3 for(int xi=0; xi<(int)avail.size(); xi++){ for(int yi=xi+1; yi<(int)avail.size(); yi++){ for(int zi=yi+1; zi<(int)avail.size(); zi++){ int i=avail[xi], j=avail[yi], k=avail[zi]; int h1=hitCount(perms[i], sc); int h2=hitCount(perms[j], sc); int h3=hitCount(perms[k], sc); array hs{h1,h2,h3}; sort(hs.begin(), hs.end()); if(hs[0]==scanHits[0] && hs[1]==scanHits[1] && hs[2]==scanHits[2]){ Sub s; s.a = {(uint8_t)i,(uint8_t)j,(uint8_t)k}; subs.push_back(s); } } } } } if(subs.empty()){ bruteForceSolveMask(info, mask, remaining); return; } // 追加クエリで絞る(最大 8 回) for(int step=0; step<8; step++){ if(g_solvedCount>=30) exit(0); if(subs.size()==1) break; // クエリ候補を少数サンプルして minimax vector qCand; qCand.reserve(120); for(int qi=0; qi<(int)perms.size(); qi++){ if(g_asked.count(perms[qi])) continue; qCand.push_back(qi); } if(qCand.empty()) break; shuffle(qCand.begin(), qCand.end(), rng); int K = min((int)qCand.size(), 25); int bestQ = qCand[0]; int bestMax = INT_MAX; for(int t=0;t cnt; int maxCode = (R==2 ? 36 : 216); cnt.assign(maxCode, 0); for(const auto& s: subs){ array hs; for(int r=0;r prevSolved){ recordSolvedString(q); g_solvedCount = rep.solvedCount; if(rep.allSolved) exit(0); return; } vector obs = extractHitsSameDigitSet(rep, prevSolved); g_solvedCount = rep.solvedCount; if(rep.allSolved) exit(0); if((int)obs.size()!=R){ bruteForceSolveMask(info, mask, remaining); return; } sort(obs.begin(), obs.end()); vector next; next.reserve(subs.size()); for(const auto& s: subs){ array hs; for(int r=0;r prevSolved){ recordSolvedString(guess); need--; }else{ // 外した → brute g_solvedCount = rep.solvedCount; if(rep.allSolved) exit(0); bruteForceSolveMask(info, mask, need); return; } g_solvedCount = rep.solvedCount; if(rep.allSolved) exit(0); } return; } // まだ複数候補なら brute bruteForceSolveMask(info, mask, remaining); } int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); // 5 数字集合(10C5=252)を列挙 vector masks; for(int m=0; m<(1<<10); m++){ if(__builtin_popcount((unsigned)m)==5) masks.push_back(m); } array info; // ---- Phase 1: 252(ただし途中で 30 本分揃ったら打ち切り)スキャン ---- int cumulative = 0; for(int mask: masks){ if(cumulative>=30) break; auto digits = digitsFromMask(mask); string q = digitsToAscendingQuery(digits); // この mask の代表(昇順) if(g_asked.count(q)) continue; // 念のため int prevSolved = g_solvedCount; Reply rep = ask(q); g_asked.insert(q); if(rep.invalid) return 0; // この mask の本数 = (h+b==5 の個数) - prevSolved int sum5 = 0; for(auto &p: rep.hb){ if(p.first + p.second == 5) sum5++; } int cnt = sum5 - prevSolved; if(cnt < 0) cnt = 0; // 念のため info[mask].cnt = cnt; info[mask].scanQuery = q; info[mask].scanHitsAll = extractHitsSameDigitSet(rep, prevSolved); if(rep.solvedCount > prevSolved){ // q がこの mask のどれかに一致して新規に解けた recordSolvedString(q); } g_solvedCount = rep.solvedCount; if(rep.allSolved) return 0; cumulative += cnt; } // ---- Phase 2: mask ごとに順列を当てに行く ---- for(int mask: masks){ int need = info[mask].cnt - g_solvedByMask[mask]; if(need<=0) continue; // 小さいところは賢く、でかいところは brute while(true){ if(g_solvedCount>=30) return 0; int rem = info[mask].cnt - g_solvedByMask[mask]; if(rem<=0) break; if(rem==1) solveSingleInMask(info[mask], mask); else if(rem==2) solveMultiInMask<2>(info[mask], mask); else if(rem==3) solveMultiInMask<3>(info[mask], mask); else { bruteForceSolveMask(info[mask], mask, rem); break; } } } // 念のため(普通ここまで来ない) return 0; }