CF2093G 题解

CF2093G 题解

套路性地想到用 01-Trie 维护二进制数来解决这类问题。具体而言有两种方法:

方法 I

这是官方题解的做法。

寻找两个整数,使得它们的异或和最大,这是经典问题。但在本题中,我们不需要异或和最大,而只需要不小于某个阈值 \(k\)。把 \(\ge k\) 这个条件看作:存在某个位置 \(i\),使得 \(x \oplus y\)\(k\) 从最高位到第 \(i + 1\) 位都相同,而 \(x \oplus y\) 的第 \(i\) 位大于 \(k\) 的第 \(i\) 位(显然 \(k\) 的第 \(i\) 位只能为 \(0\));或者 \(x \oplus y = k\)

因此我们可以枚举 \(i\) 来计算答案。具体而言,在 Trie 上维护每个节点的子树中元素编号的最大值。查询答案时,从高位到低位枚举 \(i\),沿着 \(x \oplus k\) 的路径走。如果 \(k\) 的第 \(i\) 位为 \(0\),那么尝试更新答案。

\(w = \max a_{i}\),时间复杂度 \(O(n \log w)\)AC 记录

int query(int x) {
    int u = 1, res = -1;
    for(int i = w; i >= 0; i--) {
        int x_bit = (x >> i) & 1;
        int k_bit = (k >> i) & 1;
        if(k_bit == 1) {
            if(!t[u][x_bit ^ 1]) {
                return res;
            }
            u = t[u][x_bit ^ 1];
        } else {
            if(t[u][x_bit ^ 1]) {
                res = max(res, mx[t[u][x_bit ^ 1]]);
            }
            if(!t[u][x_bit]) {
                return res;
            }
            u = t[u][x_bit];
        }
    }
    res = max(res, mx[u]);
    return res;
}

方法 II

这是 jiangly 的双指针做法。

为了方便,称满足 \(a_i \oplus a_j \ge k\) 的二元组 \((i, j)\) 为“好的”。

逐个在 Trie 上加入 \(a_i\),同时维护一个指针 \(j\),初始为 \(1\)。在 Trie 上查询使得 \(a_i \oplus y\) 最大的 \(y\),如果 \(a_i \oplus y \ge k\),则说明 \([j, i]\) 区间中有一个好的对,因此答案至多为 \(i - j + 1\)。然后在 Trie 上删除 \(a_j\),令 \(j\) 右移一位,继续查询。重复这个过程直到 \(j = i\)\([j, i]\) 区间内不存在好的对为止。

for(int i = 1, j = 1; i <= n; i++) {
    while(j <= i && tr.query(a[i]) >= k) {
        ans = min(ans, i - j + 1);
        tr.add(a[j], -1);
        j++;
    }
    tr.add(a[i], 1);
}

为什么这是正确的?如果 \((j, i)\) 是一个好的对,则答案至多为 \(i - j + 1\)。当 \(i \gets i + 1\) 时,\([1, j]\) 中的元素到 \(i\) 的距离都不小于 \(i - j + 1\) 了,可以直接忽略。因此我们删除 \(a_j\) 是正确的。

由于每个元素都被加入 Trie 一次,从 Trie 上删除至多一次,因此时间复杂度也是 \(O(n \log w)\)AC 记录

posted @ 2025-04-10 08:59  DengStar  阅读(10)  评论(0)    收藏  举报