BZOJ-2653middle 可持久化线段树 + 二分
Posted DaD3zZ
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BZOJ-2653middle 可持久化线段树 + 二分相关的知识,希望对你有一定的参考价值。
2653: middle
Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 1298 Solved: 734
[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
170337785
271451044
22430280
969056313
206452321
3
3 1 0 2
2 3 1 4
3 1 4 0
Sample Output
271451044
969056313
HINT
0:n,Q<=100
1,...,5:n<=2000
0,...,19:n<=20000,Q<=25000
Source
Solution
这道题思路相当妙啊
自己一开始看错题了,以为就是求中位数,那么显然长度为奇数时就是中间那个,长度为偶数时可能是实数,根本没法搞...
然后有一种想法很显然,就是可以通过先求出$[b,c]$段的中位数,然后贪心的从$b$到$a$,从$c$到$d$扩展这个序列,然后搞。
可是这样本质上就是暴力,所以显然不行。
以前遇到的中位数有一种很常规的搞法,就是假设中位数为$x$,那么把$<x$的赋值$-1$,把$>=x$的赋值$+1$,然后看看是否有连续的一段$Sum>=0$
这种方法显然是具有单调性的,所以又可以把枚举$x$的复杂度降到$O(logN)$
然后可以用线段树去维护连续的子段和,再维护一下左右端点,保证左右端点分别在$[a,b]$和$[c,d]$之间即可,但是这样的线段树每次需要重建,所以复杂度还是偏大。
可以考虑利用可持久化线段树,对于假定的中位数$x$,所有比他小的数为$-1$,而比他大的仍为$1$,这样就可以先建出一棵全$1$的树,然后每次再建一条$-1$的链,查询的时候前面的用$-1$,后面的用$1$,就可以达到要求了。
总复杂度$O(Qlog^{2}N)$
Code
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; inline int read() { int x=0; char ch=getchar(); while (ch<‘0‘ || ch>‘9‘) ch=getchar(); while (ch>=‘0‘ && ch<=‘9‘) {x=x*10+ch-‘0‘; ch=getchar();} return x; } #define MAXN 200010 int N,a[MAXN],Q,last; namespace PrTree { int lson[MAXN*20],rson[MAXN*20],root[MAXN],sum[MAXN*20],left[MAXN*20],right[MAXN*20],sz; inline void Update(int now) { sum[now]=sum[lson[now]]+sum[rson[now]]; left[now]=max(left[lson[now]],sum[lson[now]]+left[rson[now]]); right[now]=max(right[rson[now]],sum[rson[now]]+right[lson[now]]); } inline void BuildTree(int l,int r,int &now) { now=++sz; if (l==r) {left[now]=right[now]=sum[now]=1; return;} int mid=(l+r)>>1; BuildTree(l,mid,lson[now]); BuildTree(mid+1,r,rson[now]); Update(now); } inline void Insert(int l,int r,int &now,int fa,int pos,int val) { now=++sz; if (l==r) {sum[now]=left[now]=right[now]=val; return;} lson[now]=lson[fa],rson[now]=rson[fa]; int mid=(l+r)>>1; if (pos<=mid) Insert(l,mid,lson[now],lson[fa],pos,val); else Insert(mid+1,r,rson[now],rson[fa],pos,val); Update(now); } inline int Query(int l,int r,int L,int R,int now) { if (L>R) return 0; if (L<=l && R>=r) return sum[now]; int mid=(l+r)>>1,re=0; if (L<=mid) re+=Query(l,mid,L,R,lson[now]); if (R>mid) re+=Query(mid+1,r,L,R,rson[now]); return re; } inline int Left(int l,int r,int L,int R,int now) { if (L>R) return 0; if (L==l && R==r) return left[now]; int mid=(l+r)>>1; if (R<=mid) return Left(l,mid,L,R,lson[now]); else if (L>mid) return Left(mid+1,r,L,R,rson[now]); else return max(Left(l,mid,L,mid,lson[now]),Query(l,mid,L,mid,lson[now])+Left(mid+1,r,mid+1,R,rson[now])); } inline int Right(int l,int r,int L,int R,int now) { if (L>R) return 0; if (L==l && R==r) return right[now]; int mid=(l+r)>>1; if (R<=mid) return Right(l,mid,L,R,lson[now]); else if (L>mid) return Right(mid+1,r,L,R,rson[now]); else return max(Right(mid+1,r,mid+1,R,rson[now]),Query(mid+1,r,mid+1,R,rson[now])+Right(l,mid,L,mid,lson[now])); } }using namespace PrTree; int q[5],id[MAXN]; inline bool Check(int x) { int a=q[1],b=q[2],c=q[3],d=q[4]; return Query(1,N,b,c,root[x])+max(Right(1,N,a,b-1,root[x]),0)+max(Left(1,N,c+1,d,root[x]),0)>=0; } inline bool cmp(int x,int y) {return a[x]<a[y];} int main() { N=read(); for (int i=1; i<=N; i++) a[i]=read(),id[i]=i; stable_sort(id+1,id+N+1,cmp); stable_sort(a+1,a+N+1); BuildTree(1,N,root[1]); for (int i=2; i<=N; i++) PrTree::Insert(1,N,root[i],root[i-1],id[i-1],-1); Q=read(); while (Q--) { for (int i=1; i<=4; i++) q[i]=(read()+last)%N+1; stable_sort(q+1,q+4+1); int l=1,r=N,mid; while (l<=r) { mid=(l+r)>>1; if (Check(mid)) l=mid+1; else r=mid-1; } printf("%d\n",last=a[l-1]); } return 0; }
以上是关于BZOJ-2653middle 可持久化线段树 + 二分的主要内容,如果未能解决你的问题,请参考以下文章