P3293-[SCOI2016]美味【主席树】
正题
题目链接:https://www.luogu.com.cn/problem/P3293
题目大意
给出一个长度为\(n\)的序列,\(m\)次询问给出\(b,x,l,r\)表示询问在\([l,r]\)中找到一个数字\(a\)使得\(b\ xor\ (a+x)\)的值最大。
\(1\leq n,m\leq 2\times10^5,0\leq a,b,x<10^5\)
解题思路
往\(Trie\)上想就很容易卡思路,考虑正常情况下我们是怎么求\(b\ xor\ a\)最大的。
其实在\(Trie\)树上条等价于每次询问一个区间内有没有值然后再选择往左或者往右,现在这个\(x\)就是相当于把我们询问的区间移动了,既然\(Trie\)树解决不了这样的问题那么就考虑一下主席树。
定义一下\(z\)表示异或的那个数的基数(前面枚举的位已经确定),从大到小枚举\(i\),若\(b\)的第\(i\)位有那么最好是让\(a+x\)的第\(i\)位没有,也就是询问\([z-x,z-x+2^i)\)。如果第\(i\)位没有那么最好是\(a+x\)的第\(i\)位有,那么询问\([z-x+2^i,z-x+2^{i+1})\)就好了。
时间复杂度\(O(n\log^2 n)\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2e5+10,M=N<<5,Li=1e5;
int n,m,cnt,rt[N],w[M],ls[M],rs[M];
int Change(int x,int L,int R,int pos){
int now=++cnt;w[now]=w[x]+1;
if(L==R)return now;
int mid=(L+R)>>1;
if(pos<=mid)ls[now]=Change(ls[x],L,mid,pos),rs[now]=rs[x];
else rs[now]=Change(rs[x],mid+1,R,pos),ls[now]=ls[x];
return now;
}
int Ask(int x,int y,int L,int R,int l,int r){
if(!(w[y]-w[x]))return 0;
if(L==l&&R==r)return w[y]-w[x];
int mid=(L+R)>>1;
if(r<=mid)return Ask(ls[x],ls[y],L,mid,l,r);
if(l>mid)return Ask(rs[x],rs[y],mid+1,R,l,r);
return Ask(ls[x],ls[y],L,mid,l,mid)+Ask(rs[x],rs[y],mid+1,R,mid+1,r);
}
int Query(int L,int R,int l,int r){
if(l<0)l=0;if(r>Li)r=Li;if(r<l)return 0;
return Ask(rt[L-1],rt[R],0,Li,l,r);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);
rt[i]=Change(rt[i-1],0,Li,x);
}
while(m--){
int b,a,l,r,z=0,ans=0;
scanf("%d%d%d%d",&b,&a,&l,&r);
for(int i=17;i>=0;i--){
if((b>>i)&1){
if(Query(l,r,z-a,z-a+(1<<i)-1))
ans|=(1<<i);
else z|=(1<<i);
}
else{
if(Query(l,r,z-a+(1<<i),z-a+(1<<i+1)-1))
ans|=(1<<i),z|=(1<<i);
}
}
printf("%d\n",ans);
}
return 0;
}