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;
}

浙公网安备 33010602011771号