#include using namespace std; // ------------------------------------------------------------ // 30-parallel mixed Hit&Blow solver (interactive / local sim) // ------------------------------------------------------------ static constexpr int LEN = 5; static constexpr int DIG = 10; static constexpr int N = 30240; // 10P5 static constexpr int K = 21; // number of (hit,blow) categories struct Code { array d; uint16_t mask; // 10-bit string s; }; static int HB_ID[6][6]; static array, K> ID_HB; static inline int popcount16(uint16_t x){ return __builtin_popcount((unsigned)x); } static vector CODES; static void build_codes(){ CODES.reserve(N); 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 = (1u<(chrono::steady_clock::now()-st).count(); } }; // Simple Dinic for small graphs struct Dinic { struct Edge{ int to; int rev; int cap; }; int n; vector> g; vector level, it; Dinic(int n=0){ init(n);} void init(int n_){ n=n_; g.assign(n,{}); level.assign(n,0); it.assign(n,0);} void add_edge(int fr,int to,int cap){ Edge a{to,(int)g[to].size(),cap}; Edge b{fr,(int)g[fr].size(),0}; g[fr].push_back(a); g[to].push_back(b); } bool bfs(int s,int t){ fill(level.begin(), level.end(), -1); queue q; level[s]=0; q.push(s); while(!q.empty()){ int v=q.front(); q.pop(); for(const auto &e:g[v]) if(e.cap>0 && level[e.to]<0){ level[e.to]=level[v]+1; q.push(e.to); } } return level[t]>=0; } int dfs(int v,int t,int f){ if(v==t) return f; for(int &i=it[v]; i<(int)g[v].size(); i++){ Edge &e=g[v][i]; if(e.cap>0 && level[e.to]==level[v]+1){ int ret=dfs(e.to,t, min(f,e.cap)); if(ret>0){ e.cap-=ret; g[e.to][e.rev].cap+=ret; return ret; } } } return 0; } int max_flow(int s,int t){ int flow=0; while(bfs(s,t)){ fill(it.begin(), it.end(), 0); while(true){ int f=dfs(s,t,1e9); if(!f) break; flow += f; } } return flow; } }; // Local judge (for simulation) #ifdef LOCAL struct LocalJudge{ vector hidden; // size 30 vector already; // N int found=0; LocalJudge(uint64_t seed){ already.assign(N,0); // sample 30 distinct std::mt19937_64 rng(seed); vector all(N); iota(all.begin(), all.end(), 0); for(int i=0;i<30;i++){ int j = std::uniform_int_distribution(i, N-1)(rng); swap(all[i], all[j]); } hidden.assign(all.begin(), all.begin()+30); } array ask(int qidx){ array hist{}; for(int s: hidden){ if(already[s]){ hist[HB_ID[5][0]]++; }else{ hist[hb_cat(s,qidx)]++; } } // update already bool hit = false; for(int s: hidden) if(s==qidx){ hit=true; break; } if(hit && !already[qidx]){ already[qidx]=1; found++; } return hist; } }; #endif // ------------------------------------------------------------ // Solver // ------------------------------------------------------------ struct Solver { // state vector alive, asked, forced; vector alive_list; vector qidxs; // asked indices vector> cnt; // residual histogram for remaining unknown set vector> cats; // cats[qid][i] category vector zeroMask; // per query deque forcedQ; vector w; int found=0; Timer *timer=nullptr; double time_limit_ms = 4950.0; Solver(){ alive.assign(N,1); asked.assign(N,0); forced.assign(N,0); alive_list.resize(N); iota(alive_list.begin(), alive_list.end(), 0); w.assign(N, 0.0); } inline bool time_ok() const { return timer ? (timer->ms() < time_limit_ms) : true; } void rebuild_alive(){ vector nxt; nxt.reserve(alive_list.size()); for(int x: alive_list) if(alive[x]) nxt.push_back(x); alive_list.swap(nxt); } void add_forced(int idx){ if(!alive[idx] || asked[idx] || forced[idx]) return; forced[idx]=1; forcedQ.push_back(idx); } // Heuristic exact enumeration for the final phase. // Tries to build (some) full completions of the remaining hidden set // consistent with *all* hist constraints, and uses vote frequency // to pick a high-confidence next query. // // This is only used when the remaining count is very small, so the depth is small. int refine_by_enumeration(int n_rem, int max_solutions, double budget_ms){ if(n_rem <= 0) return -1; int m = (int)qidxs.size(); if(m == 0) return -1; if((int)alive_list.size() > 3500) return -1; if(!timer) return -1; // Keep a small safety margin. double end_ms = min(time_limit_ms - 40.0, timer->ms() + budget_ms); if(end_ms <= timer->ms()) return -1; // Copy needs (residual histograms) vector> need = cnt; // Choose a pivot query: the one that has the tightest needed bucket. int pivot = m-1; int best_bucket = INT_MAX; for(int qid=0; qid 0) local_best = min(local_best, ac[r]); if(local_best < best_bucket){ best_bucket = local_best; pivot = qid; } } vector used(N, 0); vector cur; cur.reserve(n_rem); vector freq(N, 0); int sol_cnt = 0; // A small helper: forward-check on pivot only (cheap) auto pivot_feasible = [&]()->bool{ int avail[K] = {0}; const auto &catp = cats[pivot]; for(int x: alive_list){ if(!alive[x] || used[x]) continue; avail[catp[x]]++; } for(int r=0;r avail[r]) return false; return true; }; function dfs = [&](int depth){ if(sol_cnt >= max_solutions) return; if(timer->ms() > end_ms) return; if(depth == n_rem){ sol_cnt++; for(int x: cur) freq[x]++; return; } if(!pivot_feasible()) return; // Choose next category in pivot with smallest available pool int best_r = -1; int best_sz = INT_MAX; const auto &catp = cats[pivot]; for(int r=0;r cand; cand.reserve(best_sz); for(int x: alive_list){ if(!alive[x] || used[x]) continue; if(catp[x] == best_r) cand.push_back(x); } // Prefer high-probability candidates first. sort(cand.begin(), cand.end(), [&](int a, int b){ return w[a] > w[b]; }); for(int x: cand){ if(sol_cnt >= max_solutions) break; if(timer->ms() > end_ms) break; used[x] = 1; cur.push_back(x); bool ok = true; // decrement needs for all queries for(int qid=0; qid best_f){ best_f = f; best_idx = x; } } // Use only when we have a strong signal and some sample size. if(sol_cnt >= 4 && best_idx >= 0 && best_f * 5 >= sol_cnt * 4) return best_idx; // >=80% return -1; } void compute_cats_for_query(int q){ vector v(N); for(int i=0;i nxt; nxt.reserve(alive_list.size()); const auto &cat = cats[qid]; for(int x: alive_list){ if(!alive[x]) continue; if(zm & (1u<0){ cnt[qid][c]--; if(cnt[qid][c]==0){ zeroMask[qid] |= (1u<0 && ac[r]==cnt[qid][r]) eqMask |= (1u<B edge: reverse edge cap is the used flow for(int a=0;a id a (0..K-1), col b -> id K+b (K..2K-1) constexpr int M = 2*K; uint64_t reach[M]; for(int u=0;ucol reach[a] |= (1ULL << (K+b)); } if(fa > 0){ // backward residual col->row reach[K+b] |= (1ULL << a); } } } // transitive closure (Floyd with bitsets) for(int k=0;k 0) && (reach[a] & (1ULL<<(K+b))); if(!canDec) fullMask[a] |= (1u< nxt; nxt.reserve(alive_list.size()); for(int x: alive_list){ uint8_t a = catA[x]; uint8_t b = catB[x]; if(impMask[a] & (1u<0) factor[r] = (double)cnt[qid][r] / sum[r]; else factor[r] = 1.0; } for(int x: alive_list) w[x] *= factor[cat[x]]; } } } int choose_next(){ // forced first while(!forcedQ.empty()){ int x=forcedQ.front(); forcedQ.pop_front(); if(alive[x] && !asked[x]) return x; } // otherwise IPF int sweeps = 110; if(timer){ double rem = time_limit_ms - timer->ms(); if(rem < 1000) sweeps = 60; if(rem < 350) sweeps = 25; } ipf_update(sweeps); #ifdef USE_FINISH_ENUM // Late-game refinement (experimental): enumerate a few feasible completions and vote. { int n_rem = 30 - found; if(n_rem <= 6 && (int)alive_list.size() <= 4000){ double rem_ms = timer ? (time_limit_ms - timer->ms()) : 0.0; if(rem_ms > 250.0){ int cand = refine_by_enumeration(n_rem, /*max_solutions*/28, /*budget_ms*/28.0); if(cand != -1) return cand; } } } #endif int best=-1; double bw=-1.0; for(int x: alive_list){ if(!alive[x] || asked[x]) continue; double wx = w[x]; if(wx > bw){ bw=wx; best=x; } } if(best==-1){ // fallback for(int x=0;x& hist){ // invalid query check is handled by judge (all -1,-1) in interactive; in local never. int new_found = hist[HB_ID[5][0]]; bool hit_new = (new_found > found); found = new_found; // Residual histogram for remaining unknown (excluding found ones) array resid = hist; resid[HB_ID[5][0]] -= found; // Add query qidxs.push_back(q); compute_cats_for_query(q); cnt.push_back(resid); zeroMask.push_back(0); update_zeroMask((int)qidxs.size()-1); // mark asked and remove from alive asked[q]=1; if(alive[q]) alive[q]=0; rebuild_alive(); int qid_latest = (int)qidxs.size()-1; prune_by_zeroMask(qid_latest); if(hit_new){ // peel from previous constraints peel_found(q); } // pair pruning & forced detection pair_closure_latest(); single_forced(); } }; // ------------------------------------------------------------ // Interactive I/O helpers // ------------------------------------------------------------ #ifndef LOCAL static inline bool valid_query(const string& s){ if((int)s.size()!=LEN) return false; bool used[10]={0}; for(char c: s){ if(c<'0' || c>'9') return false; if(used[c-'0']) return false; used[c-'0']=true; } return true; } #endif int main(){ // init HB mapping memset(HB_ID, -1, sizeof(HB_ID)); int id=0; for(int h=0;h<=5;h++){ for(int b=0;b<=5-h;b++){ HB_ID[h][b]=id; ID_HB[id]={h,b}; id++; } } build_codes(); #ifdef LOCAL // Local simulation benchmark int T = 30; // number of random cases (keep it moderate for local benchmarking) uint64_t seed0 = 1; long long sumQ=0; long long score=0; for(int tc=0; tc 200){ cerr << "too many queries" << endl; break; } } int Q = (int)solver.qidxs.size(); sumQ += Q; score += 100000 - Q; // (no per-case prints) } cerr << "T="< hist{}; bool invalid=false; for(int i=0;i<30;i++){ int h,b; if(!(cin>>h>>b)) return 0; if(h==-1 && b==-1) invalid=true; if(!invalid){ int cid = HB_ID[h][b]; if(cid<0) cid=0; hist[cid]++; } } if(invalid){ // must terminate immediately return 0; } // If smallest pair has hit=5, then all are (5,0) because sorted. // We don't have full list, but equivalently hist[50]==30. if(hist[HB_ID[5][0]]==30){ return 0; } solver.apply_response(q,hist); } #endif }