【洛谷P3293】美味
题目
题目链接:https://www.luogu.com.cn/problem/P3293
一家餐厅有 \(n\) 道菜,编号 \(1,2,...,n\) ,大家对第 \(i\) 道菜的评价值为 \(a_i\)。有 \(m\) 位顾客,第 \(i\) 位顾客的期望值为 \(b_i\),而他的偏好值为 \(x_i\)。因此,第 \(i\) 位顾客认为第 \(j\) 道菜的美味度为 \(b_i\text{ xor } (a_j+x_i)\),\(\text{xor}\) 表示异或运算。
第 \(i\) 位顾客希望从这些菜中挑出他认为最美味的菜,即美味值最大的菜,但由于价格等因素,他只能从第 \(l_i\) 道到第 \(r_i\) 道中选择。请你帮助他们找出最美味的菜。
\(n\leq 2\times 10^5;m\leq 10^5;0\leq a_i,b_i,x_i<10^5\)。
思路
看到异或和区间询问容易想到可持久化 01 Trie。但是这个 \(+x_i\) 很棘手,Trie 不支持加减一个数。
我们考虑一般的求异或最大值的做法:高位枚举到低位,能选择和 \(x\) 这一位不同的数字就选不同的,否则选择相同的。这样一个贪心策略显然是正确的。
假设我们求完前 \(i-1\) 位时,能与给定的数字 \(b\) 异或起来最大的是 \(ans\),考虑第 \(i\) 位,假设 \(b\) 的这一位是 \(0\),那么我们希望能选出一个数字这一位是 \(1\)。那么这个数字的取值范围应该是 \([ans+2^i,ans+2^{i+1}-1]\)。我们只需要知道序列区间 \([l,r]\) 中是否有数字在这个范围即可。用主席树维护一下就行了。
考虑 \(+x\) 的操作,不过是把区间变为了 \([ans+2^i-x,ans+2^{i+1}-1-x]\)。依然可以主席树查询。
时间复杂度 \(O(m\log n\log a)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=200010,M=100000,LG=18;
int n,m,a[N],rt[N];
struct SegTree
{
int tot,cnt[N*LG],lc[N*LG],rc[N*LG];
int update(int now,int l,int r,int k)
{
int x=++tot;
cnt[x]=cnt[now]+1; lc[x]=lc[now]; rc[x]=rc[now];
if (l==r) return x;
int mid=(l+r)>>1;
if (k<=mid) lc[x]=update(lc[now],l,mid,k);
else rc[x]=update(rc[now],mid+1,r,k);
return x;
}
int query(int nowl,int nowr,int l,int r,int ql,int qr)
{
if (ql>qr) return 0;
if (ql<=l && qr>=r) return cnt[nowr]-cnt[nowl];
int mid=(l+r)>>1,res=0;
if (ql<=mid) res+=query(lc[nowl],lc[nowr],l,mid,ql,qr);
if (qr>mid) res+=query(rc[nowl],rc[nowr],mid+1,r,ql,qr);
return res;
}
}seg;
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
rt[i]=seg.update(rt[i-1],0,M,a[i]);
}
while (m--)
{
int b,x,l,r,ans=0;
scanf("%d%d%d%d",&b,&x,&l,&r);
for (int i=LG-1;i>=0;i--)
{
int bit=((b>>i)&1)^1;
int ql=max(ans+(bit<<i)-x,0),qr=min(ans+(bit<<i)+(1<<i)-1-x,M);
if (seg.query(rt[l-1],rt[r],0,M,ql,qr)) ans+=(bit<<i);
else ans+=((bit^1)<<i);
}
cout<<(ans^b)<<"\n";
}
return 0;
}