結果

問題 No.3418 【絶望】30個並列ごちゃ混ぜHit&Blowで遊ぼう!
コンテスト
ユーザー ロロット
提出日時 2025-12-25 00:56:35
言語 C++17
(gcc 13.3.0 + boost 1.89.0)
結果
AC  
実行時間 254 ms / 5,000 ms
コード長 7,326 bytes
記録
記録タグの例:
初AC ショートコード 純ショートコード 純主流ショートコード 最速実行時間
コンパイル時間 2,732 ms
コンパイル使用メモリ 161,212 KB
実行使用メモリ 26,344 KB
スコア 9,995,214
平均クエリ数 47.86
最終ジャッジ日時 2025-12-25 01:03:52
合計ジャッジ時間 31,100 ms
ジャッジサーバーID
(参考情報)
judge2 / judge4
純コード判定しない問題か言語
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
other AC * 100
権限があれば一括ダウンロードができます

ソースコード

diff #
raw source code

// Solved by Gemini3.0
// 確率での重み付け
#include <algorithm>
#include <chrono>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <map>
#include <random>
#include <set>
#include <string>
#include <vector>

using namespace std;

struct Candidate {
    string s;
    double p;
};

// Hit & Blow Calculation
pair<int, int> 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<Candidate> 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<string> 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<double>(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<string> 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<string> 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<int>(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<double>(current_time - loop_start_time).count();
                        if (current_elapsed > time_budget) break;

                        map<pair<int, int>, 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<pair<int, int>> response(30);
        int cnt_5_0 = 0;
        map<pair<int, int>, 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<pair<int, int>, 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<Candidate> next_candidates;
        next_candidates.reserve(candidates.size());

        for (auto& c : candidates) {
            if (found_strings.count(c.s) && c.s != q) continue;

            pair<int, int> 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;
}
0