【bzoj4571: [Scoi2016]美味】区间异或和最大 ,可持久化线段树(主席树)

4571: [Scoi2016]美味

Time Limit: 30 Sec   Memory Limit: 256 MB
Submit: 704   Solved: 380
[ Submit][ Status][ Discuss]

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

对于一般的区间异或最大,就直接可持久化trie,但是该题有加法(a[j]+x[i]),便不能用可持久化trie,可以用可持久化线段树。

一个满的可持久化线段树可以看做左儿子为0,右儿子为1,希望异或和最大,则b[i]的第k位为0,那么去找第k位是否有1的,没有的话就选0(反之亦然)。从二进制的最高位开始找,便可找到最大值。

此处重点是怎么处理(a[j]+x[i]),我们把a[j]全部加入可持久化线段树里,对于第i个询问:

假设现在是做第一次操作,对第k位进行查询,b[i]的第k位为0,//  k从0开始

则我们要找是否存在一个(a[j]+x[i]) 的第k位为1,

即找是否存在  1<<k  <=  a[j]+x[i] < 1<<(k+1),

即找是否存在   1<<k -x[i] <=  a[j]  < 1<<(k+1)-x[i]

这样我们便可以在主席树上查找了。每次操作以后统计入答案里。

可参考代码(int main 里的)理解

#include<cstdio>
#include<iostream>
#define N 200005
#define M 8000005
#define INF 1<<20
using namespace std;
int rt[N],tr[M],n,m,b,x,l,r,a[N],son[M][2],sz,ans;
void insert(int &p1,int &p2,int l,int r,int x){
    if(x<l||x>r) return ;
    if(!p1) p1=++sz;
    if(!p2) p2=++sz;
    tr[p1]=tr[p2];
    tr[p1]++;
    if(l==x&&r==x) return ;
    int mid=(l+r)/2;
    if(l<=x&&x<=mid){son[p1][1]=son[p2][1];insert(son[p1][0],son[p2][0],l,mid,x);}
    else{son[p1][0]=son[p2][0];insert(son[p1][1],son[p2][1],mid+1,r,x);}
}
bool find(int p1,int p2,int l,int r,int x,int y){
    if(y<l||r<x) return 0;
    if(tr[p1]-tr[p2]==0) return 0;
    if(x<=l&&r<=y){
        return tr[p1]-tr[p2];
    }
    int mid=(l+r)/2;
    return find(son[p1][0],son[p2][0],l,mid,x,y)|find(son[p1][1],son[p2][1],mid+1,r,x,y);
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        insert(rt[i],rt[i-1],0,INF,a[i]);
    }
    for(int i=1;i<=m;i++){
        scanf("%d%d%d%d",&b,&x,&l,&r);
        ans=0;
        for(int j=19;j>=0;j--){
            if(b&(1<<j)) {
                if(!(find(rt[r],rt[l-1],0,INF,max(0,ans-x),max(0,ans+(1<<j)-x-1)))) ans+=(1<<j);
            }else if(find(rt[r],rt[l-1],0,INF,max(ans+(1<<j)-x,0),max(ans+(1<<(j+1))-x-1,0))) ans+=(1<<j);
        }
        printf("%d\n",ans^b); 
    }
}





  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值