Codeforces Round 956 F. array-value 【01Trie查询异或最小值】

题意

给定一个非负整数数组 a a a

对每个长度至少为 2 2 2 的子数组,定义其权值 为:子数组内两两异或值最小

即 b ⊂ a [ l , r ] , w ( b ) = min ⁡ l ≤ i < j ≤ r { a i ⨁ a j } b \subset a[l, r], \quad w(b) = \min_{l \leq i < j \leq r} \{a_i \bigoplus a_j\} b⊂a[l,r],w(b)=minl≤i<j≤r{ai⨁aj}

求出所有子数组中权值第 k k k 小的权值

思路

我们可以先二分答案 m i d mid mid,并且快速统计有多少个子数组的权值 ≤ m i d \leq mid ≤mid,记为 c n t cnt cnt,如果 c n t ≥ k cnt \geq k cnt≥k,说明答案可能比 m i d mid mid 更小,否则说明答案一定比 m i d mid mid 更大

问题在于如何快速统计 c n t cnt cnt? 注意到其实随着子数组越大(越长),那么其权值一定是非递增 的,也就是说更大的子数组会有更大的可能性满足权值 w ≤ m i d w \leq mid w≤mid

基于上述的观察,我们考虑先枚举区间的右端点 r p rp rp,我们要选择最大 的 l p lp lp,使得 a [ l p , r p ] a[lp, rp] a[lp,rp] 里, 任意一个右端点在 r p rp rp 的子数组,其权值都大于 m i d mid mid,也即是 a [ l p , r p ] a[lp, rp] a[lp,rp] 里任意两两的异或权值都大于 m i d mid mid,等价于两两异或的最小值 大于 m i d mid mid

又由于我们是从小到大枚举 r p rp rp 的,那么上一轮的 r p − 1 rp - 1 rp−1 就对应着其极大的 l p lp lp,如果 l p lp lp 再小(即子数组更长),那么就会破坏上面的约束,使得最小值小于等于 m i d mid mid。

因此新加入 a r p a_{rp} arp 时,我们只需要在 01    T r i e 01 \; Trie 01Trie 上对 a r p a_{rp} arp 查询异或最小值(因为前面 [ l p , r p − 1 ] [lp, rp - 1] [lp,rp−1] 两两异或值都符合要求),不难发现,我们的 l p lp lp 是非递减的,这是一个双指针的过程。

对于一对极大的 [ l p , r p ] [lp, rp] [lp,rp],右端点在 r p rp rp 的权值小于等于 m i d mid mid 的子数组数量为 l p − 1 lp - 1 lp−1,即左端点的选择范围是: [ 1 , l p − 1 ] [1, lp - 1] [1,lp−1]

当 l p lp lp 右移时(删除某些 a l p a_{lp} alp),我们只需要在 01    T r i e 01 \; Trie 01Trie 上擦除这些值即可

这样子就在 O ( n log ⁡ A ) O(n \log A) O(nlogA) 下快速统计出了权值小于等于 m i d mid mid 的子数组数量

总体时间复杂度: O ( n log ⁡ 2 A ) O(n \log ^ 2 A) O(nlog2A)

注意每次二分都要清空 T r i e Trie Trie

cpp 复制代码
#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;

const int INF=0x3f3f3f3f;
const long long INFLL=1e18;

typedef long long ll;

const int N = 100005;

struct node{
    int son[2];
    int cnt;
    int idx;
}tree[N * 32];

int tot;

void clear(){
    fore(i, 0, tot + 1){
        tree[i].cnt = tree[i].idx = 
            tree[i].son[0] = tree[i].son[1] = 0;
    }
    tot = 0;
}

void insert(int x, int i){
    int now = 0;
    for(int i = 29; i >= 0; --i){
        int ch = (x >> i & 1);
        if(!tree[now].son[ch])
            tree[now].son[ch] = ++tot;
        now = tree[now].son[ch];
        ++tree[now].cnt;
    }
    tree[now].idx = i;
}

std::pair<int, int> query(int x){ //return [mn, idx]
    int res = 0;
    int now = 0;
    for(int i = 29; i >= 0; --i){
        int ch = (x >> i & 1);
        int to = tree[now].son[ch];
        if(to && tree[to].cnt){
            now = to;
        }
        else{
            res |= (1 << i);
            now = tree[now].son[ch ^ 1];
        }
    }
    return {res, tree[now].idx}; //返回异或最小值和产生的下标i
}

void erase(int x){
    int now = 0;
    for(int i = 29; i >= 0; --i){
        int ch = (x >> i & 1);
        now = tree[now].son[ch];
        --tree[now].cnt;
    }
}

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin >> t;
    while(t--){
        int n;
        ll k;
        std::cin >> n >> k;
        std::vector<int> a(n + 1);
        fore(i, 1, n + 1) std::cin >> a[i];

        ll ans = 0;
        ll l = 0, r = 2e9;
        while(l <= r){
            clear();
            ll mid = l + r >> 1;
            int lp = 1, rp = 2;
            ll cnt = 1ll * n * (n - 1) / 2;;
            insert(a[1], 1);
            while(rp <= n){
                while(true){
                    if(lp == rp) break;
                    auto [mn, i] = query(a[rp]);
                    if(mn > mid) break;
                    while(lp <= i){
                        erase(a[lp]);
                        ++lp;
                    }
                }
                if(lp < rp){
                    cnt -= rp - lp; //减去权值大于mid的子数组数量
                }
                insert(a[rp], rp);
                ++rp;
            }

            if(cnt >= k){
                ans = mid;
                r = mid - 1;
            }
            else l = mid + 1;
        }

        std::cout << ans << endl;
        clear();
    }
    
    return 0;
}
相关推荐
Y1nhl17 分钟前
力扣_链表_python版本
开发语言·python·算法·leetcode·链表·职场和发展
qq_4017004133 分钟前
C语言中位运算以及获取低8位和高8位、高低位合并
c语言·开发语言·算法
CoovallyAIHub36 分钟前
YOLO模型优化全攻略:从“准”到“快”,全靠这些招!
深度学习·算法·计算机视觉
闻缺陷则喜何志丹41 分钟前
【BFS】 P10864 [HBCPC2024] Genshin Impact Startup Forbidden II|普及+
c++·算法·宽度优先·洛谷
MicroTech20251 小时前
微算法科技(NASDAQ: MLGO)探索Grover量子搜索算法,利用量子叠加和干涉原理,实现在无序数据库中快速定位目标信息的效果。
数据库·科技·算法
今天背单词了吗9801 小时前
算法学习笔记:8.Bellman-Ford 算法——从原理到实战,涵盖 LeetCode 与考研 408 例题
java·开发语言·后端·算法·最短路径问题
手握风云-2 小时前
优选算法的链脉之韵:链表专题
数据结构·算法·链表
Coding小公仔2 小时前
LeetCode 151. 反转字符串中的单词
开发语言·c++·算法
稳兽龙2 小时前
P1098 [NOIP 2007 提高组] 字符串的展开
c++·算法·模拟
G.E.N.2 小时前
开源!RAG竞技场(2):标准RAG算法
大数据·人工智能·深度学习·神经网络·算法·llm·rag