luogu P2839 [国家集训队]middle
这题因为一些弱智错误调了好久。。。
考虑如何判断一个数是否是一堆数的中位数(不经过排序):记你想要判断的数为\(mid\),然后把 \(\geq mid\) 的数设为\(1\), \(\leq mid\) 的数设为\(-1\),然后给这些数求个和,若\(sum<0\),则 \(mid\) 比真正的中位数大,反之则比中位数小。
是不是发现了什么,可以二分找中位数!
回到这个题,我们发现每次给区间排序是爆炸的复杂度,所以只能通过上述非排序方法来找中位数,由于数列是不变的,所以我们可以预处理一个数据结构:序列每个点根据 \(mid\) 赋值为\(1\)或\(-1\),要求支持维护区间求和,区间最大前缀,区间最大后缀(下面解释为什么)。
每次询问的区间让我们从\([a,b]\)之前选左端点,从\([c,d]\)之间选右端点,可以发现\((b,c)\)这段区间的值是必选的,所以先把他们累和。
然后我们发现如果最后答案可以比当前 \(mid\) 大,那么一定得满足你选的这段区间 \(sum>=0\),现在我们想让中位数最大,所以我们要尽量让我们选的区间的 \(sum\) 最大,我们分别把\([a,b]\)的最大后缀和\([c,d]\)的最大前缀算出来累加进去就好了。
现在的问题是如果我们对每一个 \(mid\) 建树空间复杂度为\(O(n^2)\),显然不行。但是我们又发现,\(mid\) 每次 \(+1\) 只会让那些值为 \(mid\) 的数从 \(1\) 变为 \(1\),均摊下来一共也就\(O(n)\)次变化,可以使用主席树来记录。
代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=100009;
int n,a[N],b[N],c[N],l[N],r[N],rt[N],chishi,Real[N],cnt,rev[N],m;
vector <int> v[N];
struct E
{
int l,r;
#define l(x) B[x].l
#define r(x) B[x].r
}B[N*32];
struct Dot
{
int sum,Suf,Pre;
#define s(x) A[x].sum
#define Suf(x) A[x].Suf
#define Pre(x) A[x].Pre
Dot operator + (const Dot &A)const
{
Dot B;
B.sum=A.sum+sum;
B.Pre=max(Pre,sum+A.Pre);
B.Suf=max(A.Suf,A.sum+Suf);
return B;
}
}A[N*32];
void push_up(int k)
{
if(!l(k)||!r(k)) A[k]=A[l(k)+r(k)];
else A[k]=A[l(k)]+A[r(k)];
}
Dot Query(int k,int l,int r,int x,int y);
void build(int &k,int l,int r)
{
if(!k) k=++chishi;
if(l==r)
{
s(k)=Suf(k)=Pre(k)=1;
return;
}
int mid=l+r>>1;
build(l(k),l,mid);
build(r(k),mid+1,r);
push_up(k);
}
void NewPoint(int &k,int last,int l,int r,int x,int y)
{
k=++chishi,A[k]=A[last],B[k]=B[last];
if(l==r)
{
s(k)=Suf(k)=Pre(k)=y;
return;
}
int mid=l+r>>1;
if(mid>=x)
NewPoint(l(k),l(last),l,mid,x,y);
else
NewPoint(r(k),r(last),mid+1,r,x,y);
push_up(k);
}
void init()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d",&a[i]),b[i]=a[i];
sort(b+1,b+1+n);
m=unique(b+1,b+1+n)-(b+1);
for (int i=1;i<=n;i++)
c[i]=lower_bound(b+1,b+1+m,a[i])-b,rev[c[i]]=a[i];
for (int i=1;i<=n;i++)
v[c[i]].push_back(i);
build(rt[1],1,n);
Real[1]=cnt=1;
for (int i=2;i<=m;i++)
{
for (int j=0;j<v[i-1].size();j++)
{
cnt++;
NewPoint(rt[cnt],rt[cnt-1],1,n,v[i-1][j],-1);
}
// for (int j=1;j<=n-6;j++)
// printf("%d ",Query(rt[cnt],1,n,j,j+6).Suf);puts("");
Real[i]=cnt;
}
}
Dot Query(int k,int l,int r,int x,int y)
{
if(l>=x&&r<=y)
return A[k];
int mid=l+r>>1;
if(mid>=x&&mid<y)
return Query(l(k),l,mid,x,y)+Query(r(k),mid+1,r,x,y);
if(mid>=x)
return Query(l(k),l,mid,x,y);
return Query(r(k),mid+1,r,x,y);
}
bool check(int mid,int a,int b,int c,int d)
{
a++,b++,c++,d++;
int Root=rt[Real[mid]];
int res=b<c-1?Query(Root,1,n,b+1,c-1).sum:0;
Dot Q1=Query(Root,1,n,a,b),Q2=Query(Root,1,n,c,d);
return res+Q1.Suf+Q2.Pre>=0;
}
void work()
{
int q[5],a,b,c,d,Q,last=0;
scanf("%d",&Q);
while(Q--)
{
scanf("%d %d %d %d",&a,&b,&c,&d);
q[1]=(last+a)%n,q[2]=(last+b)%n,q[3]=(last+c)%n,q[4]=(last+d)%n;
sort(q+1,q+1+4);
// printf("%d %d %d %d\n",q[1],q[2],q[3],q[4]);
int l=1,r=m,mid;
while(l<=r)
{
mid=l+r>>1;
if(check(mid,q[1],q[2],q[3],q[4]))
l=mid+1;
else
r=mid-1;
}
printf("%d\n",last=rev[r]);
}
}
int main()
{
init();
work();
return 0;
}
由于博主比较菜,所以有很多东西待学习,大部分文章会持续更新,另外如果有出错或者不周之处,欢迎大家在评论中指出!