BZOJ 4571: [Scoi2016]美味(权值线段树查询最大异或和)

4 篇文章 0 订阅
3 篇文章 0 订阅

题目大意

给定一个序列a1~an
给定若干个询问, b , x , L , R
最大化b xor (ai+x)|L<=i<=R

分析

不考虑x这个偏移量我们用二进制trie

现在考虑了有一个新操作就是权值线段树

把ai全部丢到权值线段树里面

  • 假设我们所有的数二进制长度不超过5
  • 假设我们贪心地选择了待选择的ai的前两位是10(之前选择的时候我们可以保证有这样的a存在)

考虑当前如何贪心,b的第三位是1,根据贪心我们希望a的第三位是0,那么我们就查询是否存在a的值属于[10000,10011](前两位不变,第三位为0),存在我们就把a的第三位选择为0即可,否则只有选择1。

其中b的第三位为0的时候操作差不多。

区间查询的时候加上变量就好了

注意

二进制是从0开始的,但这并不意味着你应该在高位-1
比如这里的极限是2^17
那么你就该从第17位一直考虑到0,不要妄图从16开始考虑

还有就是17位全部为1的话要用(1<<18)-1

坑了我好久啊啊啊啊啊啊啊啊啊啊啊啊

还有就是要判断空区间的情况,以及最好保证查询的区间被包含于(l,r),虽然好像只要有交集就可以弄对,但是容易出细节问题哎。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+105,oo=17,N=(1<<oo)-1;
struct SegmentTree{
    int np,rt[maxn],lc[maxn*22],rc[maxn*22],sz[maxn*22];
    void Initial()
    {
        np=0;
        memset(rt,0,sizeof(rt));
        memset(lc,0,sizeof(lc));
        memset(rc,0,sizeof(rc));
    }
    void pushup(int now)
    {
        sz[now]=sz[lc[now]]+sz[rc[now]];
    }
    void Build(int &now,int L,int R)
    {
        now=++np;
        if(L==R)
        {
            sz[now]=0;
            return;
        }
        int m=(L+R)>>1;
        Build(lc[now],L,m);
        Build(rc[now],m+1,R);
        pushup(now);
    }
    int Newnode(int now)
    {
        ++np;
        lc[np]=lc[now];rc[np]=rc[now];sz[np]=sz[now];
        return np;
    }
    void modify(int pre,int &now,int L,int R,int i)
    {
        now=Newnode(pre);
        if(L==R)
        {
            sz[now]++;
            return;
        }
        int m=(L+R)>>1;
        if(i<=m) modify(lc[pre],lc[now],L,m,i);
        else modify(rc[pre],rc[now],m+1,R,i);
        pushup(now);
    }
    int query(int now1,int now2,int L,int R,int i,int j)//[i,j]的范围大一点没有关系,只要和[l,R]有交集即可 
    {
        if(i<=L && R<=j)
            return sz[now2]-sz[now1];
        int m=(L+R)>>1,ret=0;
        if(i<=m)ret+=query(lc[now1],lc[now2],L,m,i,j);
        if(j>m) ret+=query(rc[now1],rc[now2],m+1,R,i,j);
        return ret;
    }
}sgt;
int n,m;
void Init()
{
    int a;
    sgt.Initial();
    sgt.Build(sgt.rt[0],0,N);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a);
        sgt.modify(sgt.rt[i-1],sgt.rt[i],0,N,a);
    }
}
int Q,x,l,r;
int DFS(int a,int b,int i)//这里是返回a+x的最优值 
{
    if(a==b)return a;
    int mid=(a+b)>>1;
    if( Q & (1<<i) )//第i位为1,那么我们希望a为0 
    {
        if(max(0,a-x)<=min(N,mid-x) && sgt.query(sgt.rt[l-1],sgt.rt[r],0,N,max(0,a-x),min(N,mid-x) ) )
            return DFS(a,mid,i-1);
        else
            return DFS(mid+1,b,i-1);
    }
    else
    {
        if(max(0,mid+1-x)<=min(N,b-x) && sgt.query(sgt.rt[l-1],sgt.rt[r],0,N,max(0,mid+1-x),min(N,b-x) ) )
            return DFS(mid+1,b,i-1);
        else
            return DFS(a,mid,i-1);
    }
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out1.txt","w",stdout);
    Init();
    while(m--)
    {
        scanf("%d%d%d%d",&Q,&x,&l,&r);
        printf("%d\n",Q^DFS(0, ( 1<<(oo+1) )-1 , oo ) );
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值