// Main.java import java.io.*; import java.util.*; /** * Parallel Hit & Blow (interactive) solver. * * Public API: * - interface Oracle * - static class Solver * * Local tests can implement Oracle and call: * Solver solver = new Solver(seed); * solver.selector.setParams(...); // optional * int queries = solver.run(oracle); * * For yukicoder submission, main() uses StdIOOracle (interactive). */ public class Main { // ---------- Fast Scanner (interactive) ---------- private static final class FastScanner { private final InputStream in; private final byte[] buffer = new byte[1 << 16]; private int ptr = 0, len = 0; FastScanner(InputStream in) { this.in = in; } private int readByte() throws IOException { if (ptr >= len) { len = in.read(buffer); ptr = 0; if (len <= 0) return -1; } return buffer[ptr++]; } int nextInt() throws IOException { int c; do { c = readByte(); if (c == -1) return Integer.MIN_VALUE; } while (c <= ' '); int sign = 1; if (c == '-') { sign = -1; c = readByte(); } int val = 0; while (c > ' ') { val = val * 10 + (c - '0'); c = readByte(); } return val * sign; } } // ---------- Code space (10P5) ---------- public static final class CodeSpace { public static final int N = 30240; public static final int RESP_BINS = 36; // id = 6*H + B public final int[] pack = new int[N]; // 5 nibbles packed in 20 bits public final short[] mask = new short[N]; // 10-bit digit set mask public final byte[] d0 = new byte[N]; public final byte[] d1 = new byte[N]; public final byte[] d2 = new byte[N]; public final byte[] d3 = new byte[N]; public final byte[] d4 = new byte[N]; // map packed(20-bit) -> codeId (or -1 if not in space) public final int[] packToId = new int[1 << 20]; public CodeSpace() { Arrays.fill(packToId, -1); int idx = 0; 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) { int p = (a) | (b << 4) | (c << 8) | (d << 12) | (e << 16); int m = (1 << a) | (1 << b) | (1 << c) | (1 << d) | (1 << e); pack[idx] = p; mask[idx] = (short) m; d0[idx] = (byte) a; d1[idx] = (byte) b; d2[idx] = (byte) c; d3[idx] = (byte) d; d4[idx] = (byte) e; packToId[p] = idx; idx++; } } } } } if (idx != N) throw new AssertionError("code space size mismatch: " + idx); } public int respId(int sId, int qId) { // 1. PackされたデータのXORを取る int diff = pack[sId] ^ pack[qId]; // 2. 各ニブル(4bit)が0かどうかを並列判定する // 隣り合うビットをORして、4bitのどこかが1ならそのブロックを1にする処理 int diffSig = (diff | (diff >>> 1)); diffSig |= (diffSig >>> 2); // 3. 0x11111マスクで各ニブルの最下位ビットだけ取り出し、1の数を数える // Hit数 = 5 - (不一致だった桁の数) int hit = 5 - Integer.bitCount(diffSig & 0x11111); // 4. Blow数の計算(既存のコードと同じ) int common = Integer.bitCount((mask[sId] & mask[qId]) & 0x3FF); int blow = common - hit; return hit * 6 + blow; } public String toStringCode(int id) { char[] cs = new char[5]; cs[0] = (char) ('0' + d0[id]); cs[1] = (char) ('0' + d1[id]); cs[2] = (char) ('0' + d2[id]); cs[3] = (char) ('0' + d3[id]); cs[4] = (char) ('0' + d4[id]); return new String(cs); } } // ---------- Response (judge -> solver) ---------- public static final class Response { public final int solvedCount; // # of (5,0) among 30 lines public final boolean invalid; // (-1,-1) signal or EOF public final int remaining; // R = 30 - solvedCount public final int[] c = new int[CodeSpace.RESP_BINS]; // counts for remaining only (exclude (5,0)) public Response(int solvedCount, boolean invalid) { this.solvedCount = solvedCount; this.invalid = invalid; this.remaining = 30 - solvedCount; } } // ---------- Oracle API ---------- public interface Oracle { Response ask(int qId) throws Exception; } // ---------- StdIO Oracle (interactive) ---------- public static final class StdIOOracle implements Oracle { private final FastScanner fs; private final PrintWriter out; private final CodeSpace space; public StdIOOracle(InputStream in, OutputStream out, CodeSpace space) { this.fs = new FastScanner(in); this.out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(out))); this.space = space; } @Override public Response ask(int qId) throws Exception { out.println(space.toStringCode(qId)); out.flush(); int solved = 0; boolean invalid = false; int[] cnt = new int[CodeSpace.RESP_BINS]; // read 30 lines (Hi, Bi). Always consume all lines. for (int i = 0; i < 30; i++) { int h = fs.nextInt(); int b = fs.nextInt(); if (h == Integer.MIN_VALUE || b == Integer.MIN_VALUE) { invalid = true; h = -1; b = -1; } if (h == -1 && b == -1) invalid = true; if (h == 5 && b == 0) { solved++; } else if (!invalid) { int id = h * 6 + b; if (0 <= id && id < CodeSpace.RESP_BINS) cnt[id]++; } } Response r = new Response(solved, invalid); System.arraycopy(cnt, 0, r.c, 0, cnt.length); return r; } } // ---------- Weight model ---------- private static final class WeightModel { final int n; final double[] w; int R; // remaining secrets count WeightModel(int n, int initialR) { this.n = n; this.R = initialR; this.w = new double[n]; reset(initialR); } void reset(int initialR) { this.R = initialR; double init = (double) initialR / n; Arrays.fill(w, init); } void exclude(int id) { w[id] = 0.0; } int argMaxNotZero(boolean[] asked) { int best = -1; double bestW = -1; for (int i = 0; i < n; i++) { if (asked[i]) continue; double wi = w[i]; if (wi > bestW) { bestW = wi; best = i; } } return best; } double maxNotZero(boolean[] asked) { double bestW = 0; for (int i = 0; i < n; i++) { if (asked[i]) continue; bestW = Math.max(bestW, w[i]); } return bestW; } void normalizeToR() { double sum = 0.0; for (int i = 0; i < n; i++) sum += w[i]; if (sum <= 0) return; double mul = (double) R / sum; for (int i = 0; i < n; i++) w[i] *= mul; } void updateByRegionScaling(CodeSpace space, int qId, int[] c, int newR, double eta) { this.R = newR; final int[] pack = space.pack; final short[] mask = space.mask; final int pQ = pack[qId]; final int mQ = mask[qId]; final int n = this.n; final double[] w = this.w; double[] W = new double[CodeSpace.RESP_BINS]; // Loop 1: Accumulate W for (int s = 0; s < n; s++) { double ws = w[s]; if (ws == 0.0) continue; // Inline respId calculation int diff = pack[s] ^ pQ; int diffSig = (diff | (diff >> 1)); diffSig |= (diffSig >> 2); int hit = 5 - Integer.bitCount(diffSig & 0x11111); int common = Integer.bitCount(mask[s] & mQ); int blow = common - hit; int rid = hit * 6 + blow; W[rid] += ws; } final double eps = 1e-12; double[] factor = new double[CodeSpace.RESP_BINS]; boolean isEtaOne = (Math.abs(eta - 1.0) < 1e-9); for (int r = 0; r < CodeSpace.RESP_BINS; r++) { double num = c[r] + eps; double den = W[r] + eps; if (isEtaOne) { factor[r] = num / den; } else { factor[r] = Math.pow(num / den, eta); } } double sum = 0.0; // Loop 2: Update weights for (int s = 0; s < n; s++) { double ws = w[s]; if (ws == 0.0) continue; // Inline respId calculation (re-calculate to avoid memory allocation for // checking array) int diff = pack[s] ^ pQ; int diffSig = (diff | (diff >> 1)); diffSig |= (diffSig >> 2); int hit = 5 - Integer.bitCount(diffSig & 0x11111); int common = Integer.bitCount(mask[s] & mQ); int blow = common - hit; int rid = hit * 6 + blow; double nw = ws * factor[rid]; // Check bounds (rare but consistent with original logic) if (nw > 5.0) nw = 5.0; w[s] = nw; sum += nw; } // Loop 3: Normalize if (sum > 0) { double mul = (double) R / sum; for (int i = 0; i < n; i++) w[i] *= mul; } } } // ---------- Weighted sampler ---------- private static final class Sampler { final int n; final double[] prefix; Sampler(int n) { this.n = n; this.prefix = new double[n]; } void build(double[] w) { double sum = 0.0; for (int i = 0; i < n; i++) { sum += w[i]; prefix[i] = sum; } } int sampleOne(SplittableRandom rng, double total) { double x = rng.nextDouble() * total; int lo = 0, hi = n - 1; while (lo < hi) { int mid = (lo + hi) >>> 1; if (prefix[mid] >= x) hi = mid; else lo = mid + 1; } return lo; } int[] sampleMany(SplittableRandom rng, double[] w, int T) { build(w); int[] s = new int[T]; double total = prefix[n - 1]; if (total <= 0) { for (int i = 0; i < T; i++) s[i] = rng.nextInt(n); return s; } for (int i = 0; i < T; i++) s[i] = sampleOne(rng, total); return s; } } // ---------- Query selection (approx Max-Entropy via Gini on samples) // ---------- public static final class QuerySelector { final CodeSpace space; final WeightModel model; final SplittableRandom rng; // tunables int K = 600; // candidates per step int T = 2048; // weighted samples per step int TOP = 300; // always include top-weight codes double eta = 0.7; // update learning rate double pickThreshold = 0.60; // collection start threshold // stamping to avoid clearing boolean[] each time final int[] seenStamp = new int[CodeSpace.N]; int stamp = 1; QuerySelector(CodeSpace space, WeightModel model, long seed) { this.space = space; this.model = model; this.rng = new SplittableRandom(seed); } public void setParams(int K, int T, int TOP, double eta, double pickThreshold) { if (K > 0) this.K = K; if (T > 0) this.T = T; if (TOP >= 0) this.TOP = TOP; if (eta > 0) this.eta = eta; if (pickThreshold >= 0) this.pickThreshold = pickThreshold; } int select(boolean[] asked) { double mx = model.maxNotZero(asked); // System.err.print("mx=" + mx + " "); if (mx >= pickThreshold) { int best = model.argMaxNotZero(asked); if (best != -1) return best; } Sampler sampler = new Sampler(CodeSpace.N); int[] samples = sampler.sampleMany(rng, model.w, T); int[] candidates = buildCandidates(asked); int bestQ = candidates[0]; double bestScore = -1; int[] cnt = new int[CodeSpace.RESP_BINS]; for (int qId : candidates) { double w = model.w[qId]; Arrays.fill(cnt, 0); for (int sId : samples) { int rid = space.respId(sId, qId); cnt[rid]++; } double entropy = 0; for (int r = 0; r < CodeSpace.RESP_BINS; r++) { long v = cnt[r]; double p = cnt[r] / (double) T; if (v > 0) entropy += -p * Math.log(p); } double score = entropy + w * 25; if (score > bestScore) { bestScore = score; bestQ = qId; } } return bestQ; } private int[] buildCandidates(boolean[] asked) { stamp++; if (stamp == Integer.MAX_VALUE) { Arrays.fill(seenStamp, 0); stamp = 1; } int[] top = topKNotAsked(asked, TOP); int[] cand = new int[K]; int sz = 0; for (int id : top) { if (id < 0) continue; if (asked[id]) continue; if (seenStamp[id] == stamp) continue; seenStamp[id] = stamp; cand[sz++] = id; if (sz >= K) break; } while (sz < K) { int id = rng.nextInt(CodeSpace.N); if (asked[id]) continue; if (model.w[id] == 0.0) continue; if (seenStamp[id] == stamp) continue; seenStamp[id] = stamp; cand[sz++] = id; } if (sz < K) cand = Arrays.copyOf(cand, sz); return cand; } private int[] topKNotAsked(boolean[] asked, int k) { if (k <= 0) return new int[0]; int[] ids = new int[k]; double[] val = new double[k]; Arrays.fill(ids, -1); Arrays.fill(val, -1); for (int i = 0; i < CodeSpace.N; i++) { if (asked[i]) continue; double wi = model.w[i]; if (wi <= val[k - 1]) continue; int pos = k - 1; while (pos > 0 && wi > val[pos - 1]) { val[pos] = val[pos - 1]; ids[pos] = ids[pos - 1]; pos--; } val[pos] = wi; ids[pos] = i; } return ids; } } // ---------- History Item ---------- private static final class HistoryItem { final int qId; final int[] c; // copy of response counts final int time; HistoryItem(int qId, int[] c, int time) { this.qId = qId; this.c = Arrays.copyOf(c, c.length); this.time = time; } } // ---------- Solver (public API) ---------- public static final class Solver { public final CodeSpace space = new CodeSpace(); private final WeightModel model = new WeightModel(CodeSpace.N, 30); private final boolean[] asked = new boolean[CodeSpace.N]; public final QuerySelector selector; private final List history = new ArrayList<>(); // secretFoundTime[sId] = time step when sId was found (or -1) private final int[] secretFoundTime = new int[CodeSpace.N]; public int queries = 0; public int solvedCount = 0; public Solver(long seed) { this.selector = new QuerySelector(space, model, seed); Arrays.fill(secretFoundTime, -1); } /** * Run until completion or oracle invalid. Returns number of queries performed. */ public int run(Oracle oracle) throws Exception { while (true) { int qId = selector.select(asked); if (qId < 0) return queries; asked[qId] = true; model.exclude(qId); // never ask again / never be a remaining secret queries++; Response resp = oracle.ask(qId); if (resp == null || resp.invalid) return queries; // System.err.println(resp.solvedCount > solvedCount ? 'o' : 'x'); if (resp.solvedCount > solvedCount) { secretFoundTime[qId] = queries; } if (resp.solvedCount == 30) return queries; history.add(new HistoryItem(qId, resp.c, queries)); int newR = 30 - resp.solvedCount; solvedCount = resp.solvedCount; // Update model using counts for remaining secrets only (exclude (5,0)) // Iteratively apply history updates // Reset model to uniform distribution for remaining count model.reset(30); // Re-apply exclusion for already asked queries for (int i = 0; i < CodeSpace.N; i++) { if (asked[i]) model.exclude(i); } // Temp buffer for adjusted counts int[] cAdj = new int[CodeSpace.RESP_BINS]; int iterNum = 1; if (solvedCount <= 10) { iterNum = 10; } else if (solvedCount <= 20) { iterNum = 6; } else if (solvedCount <= 25) { iterNum = 4; } else if (solvedCount <= 30) { iterNum = 2; } for (int iter = 0; iter < iterNum; iter++) { for (HistoryItem item : history) { System.arraycopy(item.c, 0, cAdj, 0, CodeSpace.RESP_BINS); // Remove counts corresponding to found secrets // But ONLY if the secret was found AFTER this history item for (int sId = 0; sId < CodeSpace.N; sId++) { if (secretFoundTime[sId] != -1 && secretFoundTime[sId] > item.time) { // This sId was part of the 'remaining' secrets when item was recorded int rid = space.respId(sId, item.qId); if (cAdj[rid] > 0) { cAdj[rid]--; } } } model.updateByRegionScaling(space, item.qId, cAdj, newR, selector.eta); } } if (queries >= 30240) return queries; } } } // ---------- Interactive entry point ---------- public static void main(String[] args) throws Exception { long seed = System.currentTimeMillis() ^ 0x9E3779B97F4A7C15L; Solver solver = new Solver(seed); solver.selector.setParams( 750, // K candidates 30240, // T samples 450, // TOP 1.0, // eta 0.21 // pickThreshold ); StdIOOracle oracle = new StdIOOracle(System.in, System.out, solver.space); solver.run(oracle); System.out.flush(); } }