题意
给定长度为
的数列
和长度为
的数列
,令矩阵
中第
行第
列的值
,每次询问给定矩形区域
,找出第
大的
。
题解
把求
大变成
小。
我最初的想法是二分答案
,然后求有多少个数比
小,依此来确定答案。观察到询问和第一维很小,我们只需要第一维暴力,第二维建可持久化Trie树就行了。但是这样是两个
的,会
,在校内OJ跑了
。
我们考虑怎么把二分去掉,仍然能求k小。
我们可以拎第一维出来,弄一堆节点一起在可持久化Trie上走。对于每一位,我们算一算有多少个数在这一位异或上这个数第一维上的数(就是异或上
)为
,记为
。如果
,我们就得出了答案这一位为
,然后把那一堆节点往异或
为
的儿子走,递归考虑下一位。否则我们得到了答案这一位为
,然后把那一堆节点往异或
为
的儿子走,把
减去
,递归考虑下一位。这样我们的复杂度就只有一个
了。这其实跟主席树求区间
小的方法很类似。
在bzoj上跑了
。。讲究
代码
#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;
}