求左端点在【a,b】,右端点在【c,d】上的最大中位数序列。考虑二分中位数。
我们令小于等于中位数的数为-1,大于中位数的数为1,如果查的区间和大于等于0表示中位数不够大,还可以变大,因为比现在二分到的值大的数比较多。现在问题变成怎么判断区间和了。
我们可以对每个数开一个线段树,记录一个区间内比该数小的数为1,我们去求这个区间和就是我们上面要找的区间和了。很显然,个每一个数开一个线段树的空间是不可行的,我们考虑动态开点的主席树来减少空间浪费。
对于那个不可行的每个点开一个线段树,我们是要记录比当前小或等于的数的位置为-1,比当前位置大的数的位置为1,也就是说我们需要先将序列排序,然后从小到大去插入信息,且插入的是他们的原数组下标,因为我们需要查找的是一个原数组区间里比当前值小的数个数。如果我们先排序再按照有序数组去建立主席树,那么对于每个数我们只需要插入log条信息,且由于主席树可以继承前面状态的主席树,实际上我们就是给每个数开了一个我们原先需要的线段树了。并且我们是从小到大插入的,也就是说到当前的数为止,前面将数置为-1的位置都是比当前数小的,也就是说是完全满足我们一开始的需求的,对于当前状态的线段树比自己小的数的位置都是-1,比自己大的数都是1.
对于二分,我们要去找第一次令区间和小于0的中位数,而不是令区间等于0的中位数。比如1234,我们要找3而不是2.二分的时候l是1,r是n+1,因为主席树的root【下标】就是排完序后的a数组的下标,mid为2就是a【2】(排完序的a数组),它对应的线段树是root【2】。因为change里面我们都是先将线段树直接继承上一个版本(直接赋值,儿子指向同一个地方),然后就改了一个位置为-1,其他都是和前面一样的,也就是说当前版本的线段树就是前缀和的结果了(和以前那些主席树不一样,不用减去之前的版本了,共用相同部分的线段树)。
对于左区间和右区间就是去求最大区间和,中间那块直接求和。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
typedef unsigned long long ull;
typedef pair<ll,ll> pii;
const int inf=0x3f3f3f3f;
const int N=2e4+10;
const int mod=1e9+7;
const ll INF=2e9+10;
#define mid ((l+r)>>1)
int n,Q,q[4],last;
struct A{
int x,id;
bool operator<(A &t){
return x<t.x;
}
}a[N];
int root[N],tot;
int ls[N*20],rs[N*20],sum[N*20],lmx[N*20],rmx[N*20];
void pushup(int u){
sum[u]=sum[ls[u]]+sum[rs[u]];
lmx[u]=max(lmx[ls[u]],lmx[rs[u]]+sum[ls[u]]);
rmx[u]=max(rmx[rs[u]],rmx[ls[u]]+sum[rs[u]]);
}
void build(int &u,int l,int r){
u=++tot;
if(l==r){
lmx[u]=rmx[u]=sum[u]=1;
return;
}
build(ls[u],l,mid);
build(rs[u],mid+1,r);
pushup(u);
}
void change(int &u,int v,int l,int r,int p,int k){
u=++tot;
ls[u]=ls[v];rs[u]=rs[v];
sum[u]=sum[v];lmx[u]=lmx[v];
rmx[u]=rmx[v];
if(l==r){
lmx[u]=rmx[u]=sum[u]=k;
return;
}
if(p<=mid) change(ls[u],ls[v],l,mid,p,k);
else change(rs[u],rs[v],mid+1,r,p,k);
pushup(u);
}
int qsum(int u,int l,int r,int x,int y){
if(x<=l&&y>=r) return sum[u];
int res=0;
if(x<=mid) res+=qsum(ls[u],l,mid,x,y);
if(y>mid) res+=qsum(rs[u],mid+1,r,x,y);
return res;
}
int qlmx(int u,int l,int r,int x,int y){
if(x<=l&&y>=r) return lmx[u];
if(y<=mid) return qlmx(ls[u],l,mid,x,y);
if(x>mid) return qlmx(rs[u],mid+1,r,x,y);
return max(qlmx(ls[u],l,mid,x,y),
qsum(ls[u],l,mid,x,y)+qlmx(rs[u],mid+1,r,x,y));
}
int qrmx(int u,int l,int r,int x,int y){
if(x<=l&&y>=r) return rmx[u];
if(y<=mid) return qrmx(ls[u],l,mid,x,y);
if(x>mid) return qrmx(rs[u],mid+1,r,x,y);
return max(qrmx(rs[u],mid+1,r,x,y),
qsum(rs[u],mid+1,r,x,y)+qrmx(ls[u],l,mid,x,y));
}
bool check(int u,int a,int b,int c,int d){
int s=0;
if(c-b>=2) s+=qsum(root[u],1,n,b+1,c-1);
s+=qrmx(root[u],1,n,a,b);
s+=qlmx(root[u],1,n,c,d);
return s>=0;
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].x;
a[i].id=i;
}
sort(a+1,a+1+n);
build(root[0],1,n);
for(int i=1;i<=n;i++)
change(root[i],root[i-1],1,n,a[i].id,-1);
cin>>Q;
while(Q--){
for(int i=0;i<4;i++) cin>>q[i];
for(int i=0;i<4;i++) q[i]=(q[i]+last)%n+1;
sort(q,q+4);
int l=0,r=n+1;//二分的是中位数的下标,在主席树里第二小就是root【2】
while(l<=r){
check(mid,q[0],q[1],q[2],q[3])?l=mid+1:r=mid-1;
}
last=a[l].x;
cout<<last<<endl;
}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}