G. Shorten the Array题解

G. Shorten the Array题解

[ G. Shorten the Array ]( Problem - G - Codeforces )

看了题解,感觉有点像是线段树中的动态开点或者主席树。

参考大佬博客: Codeforces Round 1016 (Div. 3) A-F(略讲)G(详解) - archer2333 - 博客园

思路

假设当前元素x,我们可以从二进制高位到低位逐位匹配,找到之前已经存在的元素y使得 $(x ⊕ y) \ge k $。

问题是,怎么查找呢?

首先,为了维护每个数的二进制位,我们可以利用字典树,即前缀相同的可以走同一分支,直到某一位不同后走不同分支。

那么再看回问题,对于k的某一位,如果为1,那么必须要走这一位为1的分支;如果为0,优先匹配这一位为1的分支,这样异或值一定大于等于k。

第二个问题,怎么找最小长度?

可以类比动态维护区间的最大异或值,然后有多组查询区间的那种问题。

对于上述问题,我们一般通过离线查询。

那么这题的思想类似,我们可以从第一个元素开始,边插入边查询,每次插入的时候要更新字典树的节点,对于每一个节点,还要更新这个节点出现的最后一个位置,即对于某一位为1,我们更新这一位为1的最后一个数的位置,这样我们的最小长度就可以通过当前元素的位置 减去 某一位匹配的最后一个位置 求得。

代码如下:

void solve(){
    int n,k;
    cin>>n>>k;
    int cnt=0;
    auto get_pos=[&](int x,int y){ //获得Tire节点的编号
        return (n*31+1)*y+x;
    };
    vector<int> nxt(n*62+1); // 存储Tire 子节点编号
    vector<int> pos(n*62+1); //存储某一位匹配的数最后出现的位置
    auto insert=[&](int x,int P){ //插入
        int p=0;
        for(int i=30;i>=0;i--){
            int c=x>>i&1;
            if(!nxt[get_pos(p,c)]) nxt[get_pos(p,c)]=++cnt;
            p=nxt[get_pos(p,c)];
            pos[p]=P;
        }
    };
    auto query=[&](int x,int r){ //查询
        int p=0;
        int mi=n+1;
        for(int i=30;i>=0;i--){
            int c=(x>>i&1)^1; //优先匹配异或值为1的分支
            if(k>>i&1){ //如果k的第i位为1 
                if(!nxt[get_pos(p,c)]) return mi;//如果当前已经插入的数中没有第i位异或值为1的就无法匹配
                p=nxt[get_pos(p,c)];
            }else{ //如果k的第i位为0
                if(nxt[get_pos(p,c)]){ //如果存在当前位异或值位1的分支
                    mi=min(mi,r-pos[nxt[get_pos(p,c)]]+1);
                }
                if(!nxt[get_pos(p,c^1)]) return mi; //无法继续匹配
                p=nxt[get_pos(p,c^1)];
            }
        }
        mi=min(mi,r-pos[p]+1);
        return mi;
    };
    vector<int> a(n+1);
    int len=n+1;
    for(int r=1;r<=n;r++){
        cin>>a[r];
        insert(a[r],r);
        len=min(len,query(a[r],r));
    }
    if(len==n+1) cout<<"-1"<<endl;
    else cout<<len<<endl;
}
posted @ 2025-08-08 22:27  RYRYR  阅读(7)  评论(0)    收藏  举报