2653: middle
Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 1046 Solved: 599
[ Submit][ Status][ Discuss]
Description
一个长度为n的序列a,设其排过序之后为b,其中位数定义为b[n/2],其中a,b从0开始标号,除法取下整。
给你一个长度为n的序列s。
回答Q个这样的询问:s的左端点在[a,b]之间,右端点在[c,d]之间的子序列中,最大的中位数。
其中a<b<c<d。
位置也从0开始标号。
我会使用一些方式强制你在线。
Input
第一行序列长度n。
接下来n行按顺序给出a中的数。
接下来一行Q。
然后Q行每行a,b,c,d,我们令上个询问的答案是x(如果这是第一个询问则x=0)。
令数组q={(a+x)%n,(b+x)%n,(c+x)%n,(d+x)%n}。
将q从小到大排序之后,令真正的要询问的a=q[0],b=q[1],c=q[2],d=q[3]。
输入保证满足条件。
Output
Q行依次给出询问的答案。
Sample Input
5
170337785
271451044
22430280
969056313
206452321
3
3 1 0 2
2 3 1 4
3 1 4 0
271451044
271451044
969056313
170337785
271451044
22430280
969056313
206452321
3
3 1 0 2
2 3 1 4
3 1 4 0
271451044
271451044
969056313
Sample Output
HINT
0:n,Q<=100
1,...,5:n<=2000
0,...,19:n<=20000,Q<=25000
Source
首先我们将数组排序,然后一个一个存进去建主席树……因为我们要用线段树来维护求最大子段和,需要维护sum,lmax和rmax。但是我们不可能建n棵线段树,否则空间爆炸。 在这里,我们对每棵线段树,用1表示在排序前的这个位置的数比自身大或等于自身,-1代表在排序前的这个位置的数比自身小。
然后我们会发现,如果我们按照排序的模式从小到大建树,每次只需要修改一个节点。也就是说,我们对每棵线段树,可以借用前一棵的deep=log n个结点,而自己新建的结点也只需要deep个。(如果修改的点在左子树,那么右子树只需使用上一次的同一位置的右子树即可。)
-------------------------分割线-------------------------
在这里稍微解释一下主席树,也就是可持久化线段树。
比如我们现在要维护 31 77 40 84 70 109 70 80 43 40这样一段区间,以期求得区间的第k大值,在不使用划分树的情况下,首先我们可以将这些数离散化,就变成了1 5 2 7 4 8 4 6 3 2
然后我们就可以建线段树了。在这里,我们记录的是这段区间中每个数出现的次数,从1~1,1~2,1~3,1~4...1~n(10)分别建树,每棵树的大小都是n。
那么我们第一棵树的结点就是这样的:( “{}”代表子树,"[]"代表区间,*代表值)
[root]{ [1~5] { [1~2]{ *1,*0 },[3~5] { *0, [4~5]{ *0, *0} } }, [6~10] { [6~7]{ *0,*0 },[8~10] { *0, [9~10]{ *0, *0} } }
第二棵树的结点是这样的
[root]{ [1~5] { [1~2]{ *1,*0 },[3~5] { *0, [4~5]{ *0, *1} } }, [6~10] { [6~7]{ *0,*0 },[8~10] { *0, [9~10]{ *0, *0} } }
那么,为了节约空间,我们观察这棵线段树,发现上面有色的部分都是和原来的线段树完全一样的! 所以,我们完全可以从原来的线段树里借用。
同理,第三棵树是这样的:
[root]{ [1~5] { [1~2]{ *1,*1 },[3~5] { *0, [4~5]{ *0, *1} } }, [6~10] { [6~7]{ *0,*0 },[8~10] { *0, [9~10]{ *0, *0} } }
所以我们就把每棵线段树的平均空间压缩到了O(log n)
然后解决了空间的问题,我们就发现,这个线段树是可以相加减的。也就是说,我们求u~v的区间,实际上就是seg[v]-seg[u-1]所表示的值!
这样一来,解决第k大的数的问题也很简单了。
-------------------------分割线-------------------------
建好了主席树之后,剩下来的就好办了:求左结点在[a,b]间而右节点在[c,d]间的最大子段和完全可以将[b+1,c-1]的子段和加上[a,b]的最大右子段和和[c,d]的最大左子段和。 我们二分一个数k,判断它是否有能力成为这个区间的中位数或比中位数小。显然地,当这个最大子段和>=0时,这个数一定小于或等于中位数。我们可以发现,满足条件的最大的数便是这个区间的中位数,因为但凡比它大的数子段和都要-=2
唔……码了这么多字……
#include "stdio.h"
#include "iostream"
#include "stdlib.h"
#include "algorithm"
using namespace std;
const int N=20005,Lg=17;
struct node {
int l,r; int val;
int lmx,rmx,sum;
node * s[2];
inline void ins(int _l,int _r,node* lson,node* rson){
l=_l; r=_r; s[0]=lson; s[1]=rson;
}
inline void make (int v){ val=lmx=rmx=sum=v;}
} ptree[Lg*N]; //表示主席树的点集
node* root[N]; //N个版本的根结点所在位置
int readin[N],sorted[N],n,tmp;
//分别表示排序和没排序的
int past[N]; //原来所在的位置
inline void push_up(int v){
node * c=ptree+v;
c->sum=c->s[0]->sum+c->s[1]->sum;
c->lmx=max(c->s[0]->lmx,c->s[0]->sum+c->s[1]->lmx);
c->rmx=max(c->s[1]->rmx,c->s[1]->sum+c->s[0]->rmx);
}
node* Build (int l,int r){
int k=++tmp;
ptree[k].l=l;
ptree[k].r=r;
if (l==r) {
ptree[k].make(1);
return ptree+k;
}
int mid=(l+r)>>1;
ptree[k].ins(l,r,Build(l,mid),Build(mid+1,r));
push_up(k);
return ptree+k;
}
node* build_next (node *last,int l,int r,int edit){
int k=++tmp;
ptree[k].l=l;
ptree[k].r=r;
if (l==r) {
ptree[k].make(-1);
return ptree+k;
}
int mid=(l+r)>>1;
if (edit<=mid)
ptree[k].ins
(l,r,build_next(last->s[0],l,mid,edit),last->s[1]);
else
ptree[k].ins
(l,r,last->s[0],build_next(last->s[1],mid+1,r,edit));
push_up(k);
return ptree+k;
}
void init(){
scanf("%d",&n); int i;
for (i=1;i<=n;i++)
scanf("%d",&readin[i]);
for (i=1;i<=n;i++)
sorted[i]=readin[i];
sort(sorted+1,sorted+n+1);
for (i=1;i<=n;i++){
int bot=1,top=n,mid=(1+n)>>1;
while (bot<top){
if (sorted[mid]==readin[i]) break;
if (sorted[mid]>readin[i]) top=mid-1;
else bot=mid+1;
mid=(top+bot)>>1;
}
past[mid]=i;
}
root[1]= Build (1,n);
for (i=2;i<=n;i++)
root[i]= build_next (root[i-1],1,n,past[i-1]);
}
int query_sum (node *srt,int l,int r){
int _l=srt->l,_r=srt->r,mid=(_l+_r)>>1;
if(_l==_r||(_l==l&&_r==r)) return srt->sum;
if (r<=mid) return query_sum(srt->s[0],l,r);
if (l>mid) return query_sum(srt->s[1],l,r);
return query_sum(srt->s[0],l,mid)+
query_sum(srt->s[1],mid+1,r);
}
int query_lmx (node *srt,int l,int r){
int _l=srt->l,_r=srt->r,mid=(_l+_r)>>1;
if(_l==_r||(_l==l&&_r==r)) return srt->lmx;
if (r<=mid) return query_lmx(srt->s[0],l,r);
if (l>mid) return query_lmx(srt->s[1],l,r);
int a=query_lmx(srt->s[0],l,mid);
a=max (a,query_sum(srt->s[0],l,mid)+
query_lmx(srt->s[1],mid+1,r));
return a;
}
int query_rmx (node *srt,int l,int r){
int _l=srt->l,_r=srt->r,mid=(_l+_r)>>1;
if(_l==_r||(_l==l&&_r==r)) return srt->rmx;
if (r<=mid) return query_rmx(srt->s[0],l,r);
if (l>mid) return query_rmx(srt->s[1],l,r);
int a=query_rmx(srt->s[1],mid+1,r);
a=max (a,query_sum(srt->s[1],mid+1,r)+
query_rmx(srt->s[0],l,mid));
return a;
}
bool check(int al,int ar,int bl,int br,int k){
//第k版本的线段树的al~br的最大子段和 且必须包含ar~bl段
int x;
if (ar+1<bl) x = query_sum(root[k],ar+1,bl-1);
else x = 0;
x+= query_rmx(root[k],al,ar);
x+= query_lmx(root[k],bl,br);
return x>=0;
}
int query(int al,int ar,int bl,int br){
int bot=1,top=n,mid=(1+n)>>1;
while (bot<top){
if (check(al,ar,bl,br,mid))
bot=mid;
else
top=mid-1;
mid=(top+bot+1)>>1;
}
return sorted[mid];
}
void work(){
int q,i,x=0; scanf("%d",&q);
int t[5],j;
for (i=1;i<=q;i++){
scanf("%d%d%d%d",t,t+1,t+2,t+3);
for (j=0;j<4;j++) t[j]=(t[j]+x)%n+1;
sort(t,t+4);
printf("%d\n",x=query(t[0],t[1],t[2],t[3]));
}
}
int main(){
init();
work();
return 0;
}