【洛谷P2839】middle
题目
题目链接:https://www.luogu.com.cn/problem/P2839
一个长度为 \(n\) 的序列 \(a\),设其排过序之后为 \(b\),其中位数定义为 \(b_{n/2}\),其中 \(a,b\) 从 \(0\) 开始标号,除法取下整。
给你一个长度为 \(n\) 的序列 \(s\)。
回答 \(Q\) 个这样的询问:\(s\) 的左端点在 \([a,b]\) 之间,右端点在 \([c,d]\) 之间的子区间中,最大的中位数。
其中 \(a<b<c<d\)。
位置也从 \(0\) 开始标号。
我会使用一些方式强制你在线。
\(n \leq 20000\),\(Q \leq 25000\)。
思路
很有趣的一道题。
求区间 \([l,r]\) 的中位数,可以二分答案 \(mid\),将大于等于 \(mid\) 的数字全部设为 \(1\),小于 \(mid\) 的数字全部设为 \(-1\),如果区间 \([l,r]\) 的和大于 \(0\),那么答案就不小于 \(mid\),否则答案小于 \(mid\)。
本题求左端点在 \([l_1,r_1]\),右端点在 \([l_2,r_2]\) 中最大的中位数,发现 \([r_2,l_1]\) 这一段是必须取的,然后前后各可以取一段前缀 / 后缀。
依然二分答案 \(mid\),转化为 \(1,-1\) 序列后,我们需要最大化左右两端前后缀的和使得总和尽量大,如果这个最大的和大于 \(0\),那么答案就超过 \(mid\),反之答案小于 \(mid\)。
那么可以先离散化,建立 \(n\) 棵线段树,第 \(i\) 棵线段树的区间 \([l,r]\) 表示当 \(mid=i\) 时,区间 \([l,r]\) 的和。接下来维护区间和、区间最大前缀 / 后缀和即可。
但是这样空间是 \(O(n^2)\) 的,发现第 \(i\) 棵线段树与第 \(i-1\) 棵线段树唯一有区别的地方就是离散化后数值为 \(i-1\) 的位置,总共的修改次数不会超过 \(O(n)\),所以用主席树即可。
时间复杂度 \(O((n+m)\log^2 n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=20010,LG=15,MAXN=N*LG*4;
int n,Q,tot,last,a[N],b[N],rt[N];
vector<int> pos[N];
struct SegTree
{
int tot,lc[MAXN],rc[MAXN],sum[MAXN],lmax[MAXN],rmax[MAXN];
void pushup(int x)
{
sum[x]=sum[lc[x]]+sum[rc[x]];
lmax[x]=max(lmax[lc[x]],sum[lc[x]]+lmax[rc[x]]);
rmax[x]=max(rmax[rc[x]],sum[rc[x]]+rmax[lc[x]]);
}
int build(int l,int r)
{
int x=++tot;
sum[x]=lmax[x]=rmax[x]=r-l+1;
if (l==r) return x;
int mid=(l+r)>>1;
lc[x]=build(l,mid);
rc[x]=build(mid+1,r);
return x;
}
int update(int x,int now,int l,int r,int k)
{
if (!x || x==now)
{
x=++tot;
lc[x]=lc[now]; rc[x]=rc[now];
sum[x]=sum[now]; lmax[x]=lmax[now]; rmax[x]=rmax[now];
}
if (l==k && r==k)
{
sum[x]=-1; lmax[x]=rmax[x]=0;
return x;
}
int mid=(l+r)>>1;
if (k<=mid) lc[x]=update(lc[x],lc[now],l,mid,k);
else rc[x]=update(rc[x],rc[now],mid+1,r,k);
pushup(x);
return x;
}
int query1(int x,int l,int r,int ql,int qr)
{
if (l==ql && r==qr) return sum[x];
int mid=(l+r)>>1;
if (qr<=mid) return query1(lc[x],l,mid,ql,qr);
if (ql>mid) return query1(rc[x],mid+1,r,ql,qr);
return query1(lc[x],l,mid,ql,mid)+query1(rc[x],mid+1,r,mid+1,qr);
}
int query2(int x,int l,int r,int ql,int qr)
{
if (l==ql && r==qr) return rmax[x];
int mid=(l+r)>>1;
if (qr<=mid) return query2(lc[x],l,mid,ql,qr);
if (ql>mid) return query2(rc[x],mid+1,r,ql,qr);
int sr=query1(rc[x],mid+1,r,mid+1,qr);
int mr=query2(rc[x],mid+1,r,mid+1,qr);
int ml=query2(lc[x],l,mid,ql,mid);
return max(mr,sr+ml);
}
int query3(int x,int l,int r,int ql,int qr)
{
if (l==ql && r==qr) return lmax[x];
int mid=(l+r)>>1;
if (qr<=mid) return query3(lc[x],l,mid,ql,qr);
if (ql>mid) return query3(rc[x],mid+1,r,ql,qr);
int sl=query1(lc[x],l,mid,ql,mid);
int ml=query3(lc[x],l,mid,ql,mid);
int mr=query3(rc[x],mid+1,r,mid+1,qr);
return max(ml,sl+mr);
}
}seg;
int binary(int l1,int r1,int l2,int r2)
{
int l=1,r=tot,mid;
while (l<=r)
{
mid=(l+r)>>1;
if (seg.query1(rt[mid],1,n,r1,l2)+seg.query2(rt[mid],1,n,l1,r1-1)+seg.query3(rt[mid],1,n,l2+1,r2)>=0) l=mid+1;
else r=mid-1;
}
return l-1;
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[i]=a[i];
}
sort(b+1,b+1+n);
tot=unique(b+1,b+1+n)-b-1;
for (int i=1;i<=n;i++)
{
a[i]=lower_bound(b+1,b+1+tot,a[i])-b;
pos[a[i]].push_back(i);
}
tot++;
rt[0]=seg.build(1,n);
for (int i=1;i<=tot;i++)
{
rt[i]=rt[i-1];
for (int j=0;j<pos[i-1].size();j++)
rt[i]=seg.update(rt[i],rt[i-1],1,n,pos[i-1][j]);
}
scanf("%d",&Q);
while (Q--)
{
int p[5];
scanf("%d%d%d%d",&p[1],&p[2],&p[3],&p[4]);
p[1]=(p[1]+last)%n+1; p[2]=(p[2]+last)%n+1;
p[3]=(p[3]+last)%n+1; p[4]=(p[4]+last)%n+1;
sort(p+1,p+5);
printf("%d\n",last=b[binary(p[1],p[2],p[3],p[4])]);
}
return 0;
}