BZOJ4571: [Scoi2016]美味

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

Sample Output

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

 

转载于:https://www.cnblogs.com/Yangrui-Blog/p/9683222.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值