BZOJ4571: [Scoi2016]美味
Description
一家餐厅有 n 道菜,编号 1...n ,大家对第 i 道菜的评价值为 ai(1≤i≤n)。
有 m 位顾客,第 i 位顾客的期望值为 bi,而他的偏好值为 xi 。
因此,第 i 位顾客认为第 j 道菜的美味度为 bi XOR (aj+xi),XOR 表示异或运算。
第 i 位顾客希望从这些菜中挑出他认为最美味的菜,即美味值最大的菜,但由于价格等因素,他只能从第 li 道到第 ri 道中选择。
请你帮助他们找出最美味的菜。
Input
第1行,两个整数,n,m,表示菜品数和顾客数。
第2行,n个整数,a1,a2,...,an,表示每道菜的评价值。
第3至m+2行,每行4个整数,b,x,l,r,表示该位顾客的期望值,偏好值,和可以选择菜品区间。
1≤n≤2×10^5,0≤ai,bi,xi<10^5,1≤li≤ri≤n(1≤i≤m);1≤m≤10^5
Output
输出 m 行,每行 1 个整数,ymax ,表示该位顾客选择的最美味的菜的美味值。
Sample Input
4 4
1 2 3 4
1 4 1 4
2 3 2 3
3 2 3 3
4 1 2 4
1 2 3 4
1 4 1 4
2 3 2 3
3 2 3 3
4 1 2 4
Sample Output
9
7
6
7
7
6
7
题解Here!
看到异或,就应该想到要用可持久化$Trie$树来搞搞事。
对于这道题,我们需要借鉴一下最大异或和的解题思想。
于是省去了建$Trie$树的部分。
我们想,可持久化$Trie$树的核心思想是贪心,一位一位贪心。
所以我们同样是按照数位一位一位的贪心。
因为加了一个$x$,所以我们考虑对于所有的$a_i+x$与$b$的按位异或。
假设我们已经处理到$b$的二进制第$i$位,假设是这一位上是$1$。
那么我们只需要查找是否存在$a_j+x$使得其二进制第$i$位数字是$0$即可。
由于我们已经处理了前$i-1$位了,那么设前$i-1$位结果是$ans$。
于是我们需要查找的数的大小就是在区间$[ans-x,ans+(1<<i)-1-x]$中。
手算一下就知道这个区间里的数字的第$i$位加了$x$后就都是$0$。
对于$0$同理。
那么现在我们就是要在$a_1,a_2,...,a_n$中找出是否存在于$[ans-x,ans+(1<<i)-1-x]$的数字。
这个区间范围限制,我们直接线段树就好了。
那,那个外层区间范围限制怎么整?
我们不是根据前一位推出了当前这一位嘛。。。
那就用个主席树来维护一下嘛。。。
主席树直接上板子。。。
然后就完结了。。。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #define MAXN 200010 using namespace std; int n,m,K,size=0; int val[MAXN],root[MAXN]; struct Chairman_Tree{ int sum,l,r; }a[MAXN*20]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } inline void buildtree(){ root[0]=a[0].l=a[0].r=a[0].sum=0; } void insert(int k,int l,int r,int &rt){ a[++size]=a[rt];rt=size; a[rt].sum++; if(l==r)return; int mid=l+r>>1; if(k<=mid)insert(k,l,mid,a[rt].l); else insert(k,mid+1,r,a[rt].r); } int query(int l,int r,int lside,int rside,int i,int j){ if(l<=lside&&rside<=r)return a[j].sum-a[i].sum; int mid=lside+rside>>1,ans=0; if(l<=mid)ans+=query(l,r,lside,mid,a[i].l,a[j].l); if(mid<r)ans+=query(l,r,mid+1,rside,a[i].r,a[j].r); return ans; } bool check(int l,int r,int x,int y){ x=max(0,x);y=min(y,K); if(x>y)return 0; return query(x,y,0,K,root[l],root[r]); } inline int solve(int b,int x,int l,int r){ int ans=0,now; for(int i=17;i>=0;i--){ now=ans+((1^((b>>i)&1))<<i); if(check(l-1,r,now-x,now+(1<<i)-x-1))ans=now; else ans+=((b>>i)&1)<<i; } return (ans^b); } void work(){ int l,r,x,b; while(m--){ b=read();x=read();l=read();r=read(); printf("%d\n",solve(b,x,l,r)); } } void init(){ n=read();m=read(); K=((MAXN-10)>>1); for(int i=1;i<=n;i++)val[i]=read(); buildtree(); for(int i=1;i<=n;i++){ root[i]=root[i-1]; insert(val[i],0,K,root[i]); } } int main(){ init(); work(); return 0; }