【bzoj4103】[Thu Summer Camp 2015]异或运算 【可持久化Trie树】

题意
给定长度为 n n 的数列X=x1,x2,...,xn和长度为 m m 的数列Y=y1,y2,...,ym,令矩阵 A A 中第i行第 j j 列的值Aij=xi xor yj,每次询问给定矩形区域 i[u,d],j[l,r] i ∈ [ u , d ] , j ∈ [ l , r ] ,找出第 k k 大的Aij
题解
把求 k k 大变成k小。
我最初的想法是二分答案 mid m i d ,然后求有多少个数比 mid m i d 小,依此来确定答案。观察到询问和第一维很小,我们只需要第一维暴力,第二维建可持久化Trie树就行了。但是这样是两个 log l o g 的,会 TLE T L E ,在校内OJ跑了 1.7s 1.7 s
我们考虑怎么把二分去掉,仍然能求k小。
我们可以拎第一维出来,弄一堆节点一起在可持久化Trie上走。对于每一位,我们算一算有多少个数在这一位异或上这个数第一维上的数(就是异或上 xi x i )为 0 0 ,记为sum。如果 sum<=k s u m <= k ,我们就得出了答案这一位为 0 0 ,然后把那一堆节点往异或xi 0 0 的儿子走,递归考虑下一位。否则我们得到了答案这一位为1,然后把那一堆节点往异或 xi x i
1 1 的儿子走,把k减去 sum s u m ,递归考虑下一位。这样我们的复杂度就只有一个 log l o g 了。这其实跟主席树求区间 k k 小的方法很类似。
在bzoj上跑了6.4s。。讲究
代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1005,M=300005;
int n,m,q,u,v,l,r,k,tot,A[N],B[N],x[N],y[M],root[M],siz[M*35],ch[M*35][2];
void build(int y,int &x,int dep,int v){
    x=++tot;
    siz[x]=siz[y]+1;
    ch[x][0]=ch[y][0];
    ch[x][1]=ch[y][1];
    if(dep<0){
        return;
    }
    int md=(v&(1<<dep))>0;
    build(ch[y][md],ch[x][md],dep-1,v);
}
int query(int k,int dep){
    if(dep<0){
        return 0;
    }
    int md,sum=0;
    for(int i=u;i<=v;i++){
        md=(x[i]&(1<<dep))>0;
        sum+=siz[ch[B[i]][md]]-siz[ch[A[i]][md]];
    }
    if(k<=sum){
        for(int i=u;i<=v;i++){
            md=(x[i]&(1<<dep))>0;
            A[i]=ch[A[i]][md];
            B[i]=ch[B[i]][md];
        }
        return query(k,dep-1);
    }else{
        for(int i=u;i<=v;i++){
            md=(x[i]&(1<<dep))>0;
            A[i]=ch[A[i]][!md];
            B[i]=ch[B[i]][!md];
        }
        return (1<<dep)+query(k-sum,dep-1);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&x[i]);
    }
    for(int i=1;i<=m;i++){
        scanf("%d",&y[i]);
        build(root[i-1],root[i],30,y[i]);
    }
    scanf("%d",&q);
    while(q--){
        scanf("%d%d%d%d%d",&u,&v,&l,&r,&k);
        k=(v-u+1)*(r-l+1)-k+1;
        A[0]=B[0]=0;
        for(int i=u;i<=v;i++){
            A[i]=root[l-1];
            B[i]=root[r];
        }
        printf("%d\n",query(k,30));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值