// Solved by Gemini3.0 // 確率での重み付け #include #include #include #include #include #include #include #include #include #include using namespace std; struct Candidate { string s; double p; }; // Hit & Blow Calculation pair get_hit_blow(const string& s, const string& q) { int h = 0; int b = 0; for (int i = 0; i < 5; ++i) { if (s[i] == q[i]) h++; } for (int i = 0; i < 5; ++i) { for (int j = 0; j < 5; ++j) { if (i == j) continue; if (s[i] == q[j]) b++; } } return {h, b}; } bool has_distinct_digits(const string& s) { int mask = 0; for (char c : s) { int d = c - '0'; if ((mask >> d) & 1) return false; mask |= (1 << d); } return true; } int main() { auto start_time = chrono::high_resolution_clock::now(); ios_base::sync_with_stdio(false); vector candidates; candidates.reserve(30240); for (int i = 0; i < 100000; ++i) { string s = to_string(i); while (s.length() < 5) s = "0" + s; if (has_distinct_digits(s)) { candidates.push_back({s, 1.0}); } } static mt19937 rng(12345); shuffle(candidates.begin(), candidates.end(), rng); set found_strings; while (found_strings.size() < 30) { sort(candidates.begin(), candidates.end(), [](const Candidate& a, const Candidate& b) { return a.p > b.p; }); if (candidates.empty()) break; // --- Selection Strategy --- string best_q = candidates[0].s; // Check if we need to run heuristic // If the top candidate is significantly better than the second, just take it. // This avoids "over-thinking" and missing a likely hit. bool skip_heuristic = false; if (candidates.size() > 1) { // Ratio of top probability to second top if (candidates[0].p > 1.6 * candidates[1].p) { skip_heuristic = true; } } else { skip_heuristic = true; } if (!skip_heuristic) { // Time Budget Calculation auto now = chrono::high_resolution_clock::now(); double elapsed_sec = chrono::duration(now - start_time).count(); double remaining_time = 4.90 - elapsed_sec; int estimated_remaining_queries = max(1, (int)((30 - found_strings.size()) * 1.5) + 5); double time_budget = remaining_time / estimated_remaining_queries; if (time_budget < 0.005) time_budget = 0.0; if (time_budget > 0.001) { // Identify top candidates group double max_p = candidates[0].p; vector top_candidates; for (const auto& c : candidates) { // Tighter threshold: only consider candidates extremely close to max_p if (c.p >= max_p * 0.98) { top_candidates.push_back(c.s); } else { break; } if (top_candidates.size() >= 100) break; } if (top_candidates.size() > 1) { double min_expected_size = 1e18; int sample_size = min((int)candidates.size(), 2000); vector sample; sample.reserve(sample_size); if (sample_size == (int)candidates.size()) { for (const auto& c : candidates) sample.push_back(c.s); } else { for (int i = 0; i < sample_size; ++i) { int idx = uniform_int_distribution(0, candidates.size() - 1)(rng); sample.push_back(candidates[idx].s); } } int K_missing = 30 - found_strings.size(); auto loop_start_time = chrono::high_resolution_clock::now(); for (const string& q_cand : top_candidates) { auto current_time = chrono::high_resolution_clock::now(); double current_elapsed = chrono::duration(current_time - loop_start_time).count(); if (current_elapsed > time_budget) break; map, int> buckets; for (const string& s : sample) { buckets[get_hit_blow(s, q_cand)]++; } double expected_size = 0; for (auto const& [hb, count] : buckets) { double prob_hit_bucket = 1.0 - pow(1.0 - (double)count / sample_size, K_missing); expected_size += count * prob_hit_bucket; } if (expected_size < min_expected_size) { min_expected_size = expected_size; best_q = q_cand; } } } } } string q = best_q; cout << q << endl; vector> response(30); int cnt_5_0 = 0; map, int> response_counts; for (int i = 0; i < 30; ++i) { if (!(cin >> response[i].first >> response[i].second)) return 0; if (response[i] == make_pair(5, 0)) cnt_5_0++; response_counts[response[i]]++; } bool q_is_newly_found = false; if (cnt_5_0 > (int)found_strings.size()) { found_strings.insert(q); q_is_newly_found = true; } if (found_strings.size() == 30) break; // Update Weights int previously_found_count = found_strings.size() - (q_is_newly_found ? 1 : 0); if (response_counts.count({5, 0})) { response_counts[{5, 0}] -= previously_found_count; if (response_counts[{5, 0}] <= 0) response_counts.erase({5, 0}); } map, double> weight_sums; for (const auto& c : candidates) { if (found_strings.count(c.s) && c.s != q) continue; weight_sums[get_hit_blow(c.s, q)] += c.p; } vector next_candidates; next_candidates.reserve(candidates.size()); for (auto& c : candidates) { if (found_strings.count(c.s) && c.s != q) continue; pair hb = get_hit_blow(c.s, q); double bucket_count = response_counts.count(hb) ? response_counts[hb] : 0; if (bucket_count > 0 && weight_sums[hb] > 0) { c.p *= (bucket_count / weight_sums[hb]); if (c.p > 1e-15 && !(c.s == q && q_is_newly_found)) { next_candidates.push_back(c); } } } candidates = next_candidates; double sum_p = 0; for (const auto& c : candidates) sum_p += c.p; if (sum_p > 0) { double scale = (30 - found_strings.size()) / sum_p; for (auto& c : candidates) c.p *= scale; } } return 0; }