QOJ.ac

QOJ

Type: Editorial

Status: Open

Posted by: jiangly

Posted at: 2025-12-13 00:38:17

Last updated: 2025-12-13 00:38:20

Back to Problem

题解

题意

交互题。有 $N$ ($1\le N\le 4.3\cdot10^4$) 种矿石混在了一起,每种恰好有两块。有一个机器,初始为空,你可以加入/移除一块矿石,并询问机器里的矿石种类数量。求出哪 $N$ 对矿石种类相同,总操作数不超过 $10^6$。

题解

假设已知矿石能分为 $X$, $Y$ 两个集合,每个集合内有每种种类的矿石各一个,我们可以采取如下的分治策略:

  • 选择 $X$ 集合中的一半矿石 $L$ 加入机器,然后依次加入 $Y$ 集合的每个矿石,如果种类数量没有改变,则与它配对的在 $L$ 中,否则在 $X\setminus L$ 中。将矿石全部移除,递归求解。

总询问次数 $3N\log N$,得分 $25$。

如果矿石被打乱了顺序,只需要依次加入所有矿石,如果种类数没有改变,说明与它配对的矿石在前面,否则在后面。这样就能把矿石分成上文所述的两个集合。加上清除矿石的代价,总询问次数 $4N+3N\log N$,得分 $40$。

接下来,可以发现有一些移除矿石的步骤是不必要的:

  • $Y$ 集合的每一个矿石是否在机器中并不重要,我们只关心加入/移除矿石时种类数是否改变。
  • 对于 $X$ 集合中的矿石,不论是 $L$ 在机器中,或是 $X\setminus L$ 在机器中,我们都能够确定与 $Y$ 集合的每一个矿石配对的矿石的位置。

至此,总询问次数减少到了 $2N+1.5N\log N$,得分 $80$。

最后是一些小优化:

  • 如果确定与 $L$ 集合中的矿石配对的矿石数量已经等于 $|L|$,则剩余的 $Y$ 集合中的矿石都与 $X\setminus L$ 中的矿石配对;对于 $X\setminus L$ 同理;这样,我们得以在每次递归中节约至少 $1$ 次询问。
  • 假设每次划分 $X$ 集合的比例为 $p$ 和 $1-p$,则总询问次数的递归式为 $T(N)=T(pN)+T((1-p)N)+(1+p)N$,经计算可得 $p=\frac{3-\sqrt5}2$ 时最优。

最终,总询问次数约为 $1.44N\log N$。

代码

#include "minerals.h"
#include <vector>
#include <numeric>
bool query(int x) {
    static int last = 0;
    int v = Query(x);
    if (v == last) {
        return false;
    } else {
        last = v;
        return true;
    }
}
void work(std::vector<int> x, std::vector<int> y, bool in) {
    if (int(x.size()) == 1) {
        Answer(x[0], y[0]);
    } else {
        int m = std::max(1, int(0.382 * x.size()));
        std::vector<int> yl, yr;
        for (int i = 0; i < m; ++i)
            query(x[i]);
        for (auto i : y) {
            if (int(yl.size()) == m) {
                yr.push_back(i);
            } else if (int(yr.size()) == int(x.size()) - m) {
                yl.push_back(i);
            } else if (query(i) != in) {
                yr.push_back(i);
            } else {
                yl.push_back(i);
            }
        }
        work(std::vector<int>(x.begin(), x.begin() + m), yl, !in);
        work(std::vector<int>(x.begin() + m, x.end()), yr, in);
    }
}
void Solve(int n) {
    std::vector<int> x, y;
    std::vector<bool> vis(2 * n);
    for (int i = 0; i < 2 * n; ++i)
        if (query(i + 1))
            vis[i] = true;
    for (int i = 0; i < 2 * n; ++i) {
        if (vis[i]) {
            x.push_back(i + 1);
        } else {
            y.push_back(i + 1);
        }
    }
    work(x, y, true);
}

Comments

No comments yet.