1 条题解

  • 0
    @ 2026-5-5 22:01:41

    题解(困难版本)

    本题要求用不超过 10n10n 次查询找出隐藏排列 ppn4n \ge 4piip_i \neq i)。查询方式为给定排列 qq,返回满足 i<ji<jpqi=qjp_{q_i}=q_jiki \neq k(i,j)(i,j) 对数,其中 kk 是程序开始时选定的常数。简单版本中每次二分候选集需要两次查询,总次数约 2nlog2n2n \log_2 n,对于 n=100n=100 可能达到 14001400 次,超过 10n10n 限制。困难版本通过三分法将每次迭代的候选集缩小为原来的 1/31/3,总查询次数降为约 2nlog3n8.4n2n \log_3 n \approx 8.4n,满足要求。


    核心思想:利用 A,B,CA,B,C 区域的三分

    选定 k=n/3+1k = \lfloor n/3 \rfloor + 1,并将下标划分为三个区域:

    • A=[1,k1]A = [1,\, k-1]
    • B=[k+1,nk]B = [k+1,\, n-k]
    • C=[nk+1,n]C = [n-k+1,\, n]

    对于某个目标值 ii(要找 jj 使得 pj=ip_j = i),将当前候选集均分为三份 P1,P2,P3P_1, P_2, P_3,分别填充到 A,B,CA, B, C 区域,其余位置任意填放,并令 qk=iq_k = i

    进行两次查询:

    1. 正向查询用构造好的 qq,得答案 aa
    2. qq 中除位置 kk 以外的所有元素反转,查询得 bb

    分析边 (j,i)(j, i)pj=ip_j = i)的计数情况:

    • jAj \in A:正向查询时 j<kj < kjkj \neq k,入边被计数;反向查询时 jj 被换到 CC 区(索引 >k>k),不再满足 j<kj<k,不计。故入边贡献 (a,b)=(1,0)(a,b) = (1,0)
    • jCj \in C:正向不计,反向计,贡献 (0,1)(0,1)
    • jBj \in B:无论正反,jj 始终 >k>k,不满足 j<kj<k,贡献 (0,0)(0,0)

    其他边(不涉及 ii 的边)在正反查询中的总贡献为一个固定常数 CC,且两次中相同。因此:

    • jAj \in Aa+b=C+1a+b = C+1ab=1a-b = 1
    • jBj \in Ba+b=Ca+b = Cab=0a-b = 0
    • jCj \in Ca+b=C+1a+b = C+1ab=1a-b = -1

    观察差值 d=abd = a-bd=1d=1 对应 AAd=0d=0 对应 BBd=1d=-1 对应 CC。只需两次查询即可唯一确定 jj 所在的三分之一候选集。每次迭代候选集大小缩小为原来的 1/31/3,需要 log3(n1)\lceil \log_3 (n-1) \rceil 次迭代。每个目标值 ii 消耗 2log3(n1)2 \lceil \log_3 (n-1) \rceil 次查询。

    对于 n100n \le 100log3994.2\log_3 99 \approx 4.2,最坏情况下每个 ii 需要 1010 次查询,总查询次数不超过 10n10n,恰好满足限制。


    算法步骤

    1. 读入 nn,设定 k=n/3+1k = \lfloor n/3 \rfloor + 1,输出 kk
    2. 初始化排列 pp 全未知。
    3. 对于每个 i[1,n]i \in [1,n],找出 jj 使得 pj=ip_j = i
      • 候选集 SS 初始为 {1,,n}{i}\{1,\dots,n\} \setminus \{i\}(因为 piip_i \neq i,但即使 ii 可能是自己的像,根据条件也排除,不过题目保证 piip_i \neq i,可大胆排除)。
      • S>1|S| > 1
        • SS 均分为三份 S1,S2,S3S_1, S_2, S_3
        • 构造 qq:将 S1S_1 填入 AA 区,S2S_2 填入 BB 区,S3S_3 填入 CC 区,剩余位置用不在 S{i}S \cup \{i\} 的元素填充。令 qk=iq_k = i
        • 询问 qqaa;反转 qq(除 kk)询问得 bb;计算 d=abd = a - b
        • d=1d = 1SS1S \leftarrow S_1;若 d=0d = 0SS2S \leftarrow S_2;若 d=1d = -1SS3S \leftarrow S_3
      • 最终 S={j}S = \{j\},记录 pj=ip_j = i
    4. 输出排列 pp

    查询次数:2nlog3(n1)10n2n \lceil \log_3 (n-1) \rceil \le 10n,可通过。


    复杂度分析

    • 查询次数:O(nlogn)O(n \log n),常数约为 2/log231.262 / \log_2 3 \approx 1.26,实际总次数约 1.26nlog2n1.26 \, n \log_2 n,在 n=100n=100 时远小于 10001000
    • 时间复杂度:O(n2logn)O(n^2 \log n)(构造排列等),但 n100n \le 100 足够。
    • 空间复杂度:O(n)O(n)

    参考代码

    #include <bits/stdc++.h>
    using namespace std;
    
    int n, k;
    
    int query(vector<int> &q) {
        cout << "? ";
        for (int i = 1; i <= n; i++) cout << q[i] << " \n"[i == n];
        cout.flush();
        int res; cin >> res;
        return res;
    }
    
    void solve() {
        cin >> n;
        k = n / 3 + 1;                // 选择 k
        cout << k << endl;
    
        vector<int> p(n + 1, 0);
        int lenA = k - 1, lenC = n - k - (n - 2*k); // 实际为 k-1 和 k? 简化计算
        // 实际区域大小:A: [1, k-1], C: [n-k+1, n], 大小均为 k-1? 若 n=3k? 这里仅作示意,具体需精确计算。
        // 更安全的做法:动态分配三元组。
        // 代码省略具体分配细节,核心为上述三分逻辑。
    }
    
    int main() {
        int t; cin >> t;
        while (t--) solve();
        return 0;
    }
    

    注意:实际实现需根据 nn 精确计算各区域大小,确保三段容量足够。由于 n100n \le 100,也可为每个 ii 单独动态分配。上述伪代码展示了三分法的核心流程。

    • 1

    信息

    ID
    6961
    时间
    1000ms
    内存
    256MiB
    难度
    6
    标签
    递交数
    1
    已通过
    1
    上传者