[NOI 2026] 布丁 96pts 题解

[NOI 2026] 布丁 96pts 题解

题意简述

交互题, 给定值域 \(m (m \le 3000)\) 以及若干数 \(w\) .

\(t \le 3000\) 次提问, 每次在交互库内部钦定一个值 \(w\), 你可以进行若干次询问, 每次给出一个序列, 交互库会将 \(w\) 插入你的序列中并排序, 返回每相邻两个数的 \(gcd\) 的和.

做法

考虑决策树. 在决策树上的每个节点存储:

  1. 当前的查询操作
  2. 当前的候选值

随机构造若干个序列, 然后按信息熵排序, 取信息熵最高的序列并作为本节点的查询序列.

对所有候选值进行虚拟查询, 然后将查询结果相同的节点归入同一节点. 依次向下生长, 直到:

  1. 此节点候选数列长度小于你的生成序列长度(9)
  2. 此节点深度为 \(8\) (很重要, 你需要保证查询次数为 \(4\) 因为 \(5\) 会扣很多分)

叶子节点的查询序列就是候选序列.

这样分数不够好, 那么我们需要做生成的查询序列的随机数池.

保证随机数池的质因子足够小, 这样可以让 \(gcd\) 比较大且不会那么奇奇怪怪.

我的池子是 \(2, 3, 5, 7, 11, 13\) 的所有倍数.

由此, 生成一颗决策树并拿到 \(90\) 左右的分数, 上限 \(96pts\).

Talk is Cheap

#include <bits/stdc++.h>
#include "pudding.h"
using namespace std;

namespace OI {

random_device rd;
mt19937 gen(rd());

inline int randint(int l, int r) {
    uniform_int_distribution<> dis(l, r);
    return dis(gen);
}

int pool[] = {
    2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 22, 24,
    25, 26, 27, 28, 30, 32, 33, 35, 36, 39, 40, 42, 44, 45, 48, 49, 50, 52,
    54, 55, 56, 60, 63, 64, 65, 66, 70, 72, 75, 77, 78, 80, 81, 84, 88, 90,
    91, 96, 98, 99, 100, 104, 105, 108, 110, 112, 117, 120, 121, 125, 126,
    128, 130, 132, 135, 140, 143, 144, 147, 150, 154, 156, 160, 162, 165,
    168, 169, 175, 176, 180, 182, 189, 192, 195, 196, 198, 200, 208, 210,
    216, 220, 224, 225, 231, 234, 240, 242, 243, 245, 250, 252, 256, 260,
    264, 270, 273, 275, 280, 286, 288, 294, 297, 300, 308, 312, 315, 320,
    324, 325, 330, 336, 338, 343, 350, 351, 352, 360, 363, 364, 375, 378,
    384, 385, 390, 392, 396, 400, 405, 416, 420, 429, 432, 440, 441, 448,
    450, 455, 462, 468, 480, 484, 486, 490, 495, 500, 504, 507, 512, 520,
    525, 528, 539, 540, 546, 550, 560, 567, 572, 576, 585, 588, 594, 600,
    605, 616, 624, 625, 630, 637, 640, 648, 650, 660, 672, 675, 676, 686,
    693, 700, 702, 704, 715, 720, 726, 728, 729, 735, 750, 756, 768, 770,
    780, 784, 792, 800, 810, 819, 825, 832, 840, 845, 847, 858, 864, 875,
    880, 882, 891, 896, 900, 910, 924, 936, 945, 960, 968, 972, 975, 980,
    990, 1000, 1001, 1008, 1014, 1024, 1029, 1040, 1050, 1053, 1056, 1078,
    1080, 1089, 1092, 1100, 1120, 1125, 1134, 1144, 1152, 1155, 1170, 1176,
    1188, 1200, 1210, 1215, 1225, 1232, 1248, 1250, 1260, 1274, 1280, 1287,
    1296, 1300, 1320, 1323, 1331, 1344, 1350, 1352, 1365, 1372, 1375, 1386,
    1400, 1404, 1408, 1430, 1440, 1452, 1456, 1458, 1470, 1485, 1500, 1512,
    1521, 1536, 1540, 1560, 1568, 1575, 1584, 1600, 1617, 1620, 1625, 1638,
    1650, 1664, 1680, 1690, 1694, 1716, 1728, 1750, 1755, 1760, 1764, 1782,
    1792, 1800, 1815, 1820, 1848, 1872, 1875, 1890, 1911, 1920, 1925, 1936,
    1944, 1950, 1960, 1980, 2000, 2002, 2016, 2025, 2028, 2048, 2058, 2079,
    2080, 2100, 2106, 2112, 2156, 2160, 2178, 2184, 2187, 2197, 2200, 2205,
    2240, 2250, 2268, 2288, 2304, 2310, 2340, 2352, 2376, 2400, 2401, 2420,
    2430, 2450, 2457, 2475, 2496, 2500, 2520, 2548, 2560, 2574, 2592, 2600,
    2625, 2640, 2646, 2695, 2700, 2704, 2730, 2744, 2750, 2772, 2800, 2808,
    2835, 2860, 2880, 2912, 2916, 2925, 2940, 2970, 3000
};
const int POOL_SIZE = sizeof(pool) / sizeof(pool[0]);

int simulate(const vector<int>& a, int w) {
    vector<int> b;
    b.reserve(a.size() + 1);
    int i = 0;
    while (i < (int)a.size() && a[i] <= w) {
        b.push_back(a[i]);
        ++i;
    }
    b.push_back(w);
    while (i < (int)a.size()) {
        b.push_back(a[i]);
        ++i;
    }
    int res = 0;
    for (int j = 1; j < (int)b.size(); ++j) {
        res += __gcd(b[j - 1], b[j]);
    }
    return res;
}

double entropy(const map<int, int>& cnt, int total) {
    double ret = 0.0;
    for (auto p : cnt) {
        double prob = p.second * 1.0 / total;
        ret -= prob * log2(prob);
    }
    return ret;
}

struct Node {
    vector<int> cand;
    vector<int> query;
    map<int, int> child;
    int depth = 0;
};
vector<Node> tree;
const int TRIES = 8000; // 当值为 8000 的时候上过 96, 但是这个数值可能会因为评测机波动导致 TLE, 建议开 5000

int buildNode(const vector<int>& cand, int depth = 0) {
    int u = tree.size();
    tree.push_back({cand, {}, {}, depth});
    
    if (cand.size() <= 8 || depth == 3) {
        tree[u].query = cand;
        return u;
    }
    
    double bestEntropy = -1e9;
    vector<int> bestQuery;
    
    for (int tries = 0; tries < TRIES; ++tries) {
        set<int> qset;
        
        while ((int)qset.size() < 8) {
            qset.insert(pool[randint(0, POOL_SIZE - 1)]);
        }
        vector<int> query(qset.begin(), qset.end());
        
        map<int, int> cnt;
        for (int x : cand) {
            cnt[simulate(query, x)]++;
        }
        double ent = entropy(cnt, cand.size());
        
        if (ent > bestEntropy) {
            bestEntropy = ent;
            bestQuery = query;
        }
    }
    
    tree[u].query = bestQuery;
    
    map<int, vector<int>> groups;
    for (int x : cand) {
        int val = simulate(bestQuery, x);
        groups[val].push_back(x);
    }
    
    for (auto& pr : groups) {
        int val = pr.first;
        const vector<int>& childCand = pr.second;
        int v = buildNode(childCand, depth + 1);
        tree[u].child[val] = v;
    }
    
    return u;
}

int root = -1;

} // namespace OI

using namespace OI;

void init(int, int) {
    vector<int> all;
    for (int i = 1; i <= 3000; ++i)
        all.push_back(i);
    tree.clear();
    root = buildNode(all);
}

int find_tastiness(int c, int m) {
    int u = root;
    while (true) {
        int ret = query_tastiness(tree[u].query);
        int s = u;
        u = tree[u].child[ret];
        if (!u) {
            for (int x : tree[s].cand)
                if (ret == simulate(tree[s].query, x))
                    return x;
        }
    }
    return -1;
}

image

posted @ 2026-07-27 20:11  Kibrel  阅读(15)  评论(0)    收藏  举报