题意:给定一个序列,每次询问左端点在[a,b]中,右端点在[c,d]中的子序列中,最大的中位数。强制在线。
前几天做到与这道题有一点点类似的。那道题是给定一个序列,求多少个子序列的中位数大于等于给定值M。
这两道题中的中位数的定义都是一样的。设序列S从大到小排序后为S[1..k],中位数为M=S[k/2向上取整]。
那就意味着,在序列S中,大于等于M的数大于等于k/2。如果把序列S转化为T,T[i]=1(S[i]>=M),-1(S[i]<M),则ΣT[i]一定非负。
同理,如果对于任意一个M。将序列S转化为T之后,如果ΣT[i]非负。那么这个序列的中位数一定大于等于M。
回到这道题。我们可以先考虑一个弱版:只询问一次。
有了上面那道题的经验。对于M<M',如果发现中位数大于等于M',那一定也大于等于M。所以有单调性,很容易就想到了二分。二分M。得到T[]。因为要求了左端点和右端点。所以,如果能在满足要求的情况下找到这一段ΣT[i..j](i∈[a,b],j∈[c,d])非负,那么中位数一定就大于等于M了。所以,我们只需要求出符合要求的最大子序列和,看是不是非负即可。
感觉回到了GSS。线段树维护lmax,rmax,sum即可。因为有a<b<c<d,所以[a,b],[c,d]是互不包含的。最大连续子序列和就是[a,b]的rmax+(b,c)的sum+[c,d]的lmax。
另外n个数都是要离散化的这是必然的。二分的时候二分下标。
现在感觉已经很明了了。这样的话。二分logn。重建序列T和线段树nlogn。总复杂度是O(nlog^2n)。可以很好的解决一次询问。= =
多个询问怎么办。O(nqlog^2n)作死?其实瓶颈在这:为什么每次都要重建线段树?根本就不需要。
假设离散化的时候数值是从小到大排序的。那我们可以这么想,二分到M时,排序后的下标第1~M-1位上都是-1。第M~n位上都是1。然后还是跟上面一样的方法判定。
这就要用到函数式编程的思想。在原来的基础上不做修改,继续建树,也就是函数式线段树。
具体来说:初始的树,每个位置都是1。序列从小到大插入主席树时,修改第i棵树的时候,第i-1大的数的位置上改为-1。这样以此类推,就有n棵不同版本的线段树。
二分到M时,就在第M棵线段树上查询。可以保证此时下标为1~M-1的位置上的数都是-1。其余位置上都是1。这样就可以进行查询了。
这样就没问题了。二分logn。建函数式线段树nlogn。查询一次logn。总复杂度(nlogn+qlog^2n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; struct node{ int sum,lmax,rmax; node(){}; node( int s, int l, int r):sum(s),lmax(l),rmax(r){}; }seg[5000000],z; int lc[5000000],rc[5000000],T[30000],q[4],ans=0,tot=0,n,Q; pair< int , int >a[30000]; char buf[4000000],*pt=buf,*o=buf; inline int getint(){ int s=0; while (*pt< '0' ||*pt> '9' )pt++; while (*pt>= '0' &&*pt<= '9' )s=s*10+*pt++-48; return s; } inline void print( int x){ char str[12],*p=str; if (!x)*o++=48; else { while (x) *p++=x%10+48,x/=10; while (p--!=str)*o++=*p;}*o++= '\n' ; } inline node operator + ( const node &a, const node &b){ return node(a.sum+b.sum,max(a.lmax,a.sum+b.lmax),max(b.rmax,b.sum+a.rmax)); } void build( int l, int r, int &rt){ rt=++tot; if (l==r){ seg[rt]=node(1,1,1); return ;} int m=l+r>>1; build(l,m,lc[rt]); build(m+1,r,rc[rt]); seg[rt]=seg[lc[rt]]+seg[rc[rt]]; } void modify( int p, int k, int v, int l, int r, int &rt){ rt=++tot; lc[rt]=lc[p]; rc[rt]=rc[p]; if (l==r){ seg[rt]=node(v,v,v); return ;} int m=l+r>>1; if (k<=m) modify(lc[p],k,v,l,m,lc[rt]); else modify(rc[p],k,v,m+1,r,rc[rt]); seg[rt]=seg[lc[rt]]+seg[rc[rt]]; }node ask( int L, int R, int l, int r, int rt){ if (L>R) return node(0,0,0); if (L==l&&r==R) return seg[rt]; int m=l+r>>1; if (R<=m) return ask(L,R,l,m,lc[rt]); else if (L>m) return ask(L,R,m+1,r,rc[rt]); else return ask(L,m,l,m,lc[rt])+ask(m+1,R,m+1,r,rc[rt]); } inline int check( int k){ return ask(q[0],q[1],0,n-1,T[k]).rmax+ask(q[1]+1,q[2]-1,0,n-1,T[k]).sum+ask(q[2],q[3],0,n-1,T[k]).lmax>=0; } int main(){ fread (buf,1,4000000,stdin); n=getint(); for ( int i=0;i<n;i++) a[a[i].second=i].first=getint(); sort(a,a+n); build(0,n-1,T[0]); for ( int i=1;i<n;i++) modify(T[i-1],a[i-1].second,-1,0,n-1,T[i]); Q=getint(); while (Q--){ q[0]=(getint()+ans)%n; q[1]=(getint()+ans)%n; q[2]=(getint()+ans)%n; q[3]=(getint()+ans)%n; sort(q,q+4); int l=0,r=n-1,mid; while (l<=r) if (check(mid=l+r>>1)) l=mid+1; else r=mid-1; print(ans=a[l-1].first); } return fwrite (buf,1,o-buf,stdout),0; } |
题意:给定一颗树。询问一个子树中第k大的节点。
【终于在0:02的时候A掉了=。=】
这几天复习一下数据结构。先复习POJ2104。写可持久化线段树练手先。
然后到了这道题。其实也蛮裸的。
先求出dfs序。记录每个节点开始和结束的时间戳。然后就相当于每个点开始到结束这个区间里求第2*k大。
然后就是一个主席树了。
根据JC大神的指点结束的时间戳不用占位置了,根据下一个开始的时间戳减一就行。这样可以省一半的空间,也可以省很多时间。
由于spoj太慢加了一个read。后来一不小心就A了。速度还行吧。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int w[200010],lc[2100010],rc[4100010],bin[100010],p[200010],n1[200010],h[100010],ee=0,tot; int id[100010],sum[2100010],T[100010],num[100010],begin[100010],end[100010],n,c=0; void read( int &x){ char ch; for (ch= getchar ();ch< '0' ||ch> '9' ;ch= getchar ()); x=ch- '0' ; for (ch= getchar ();ch>= '0' &&ch<= '9' ;ch= getchar ())x=x*10+ch-48; } inline void ae( int a, int b){ p[ee]=b; n1[ee]=h[a]; h[a]=ee++; p[ee]=a; n1[ee]=h[b]; h[b]=ee++; } void dfs( int u, int fa){ num[begin[u]=++c]=w[u]; for ( int i=h[u];~i;i=n1[i]) if (p[i]!=fa) dfs(p[i],u); end[u]=c; } void build( int l, int r, int &rt){ rt=++tot; sum[rt]=0; if (l==r) return ; int m=l+r>>1; build(l,m,lc[rt]); build(m+1,r,rc[rt]); } void modify( int p, int v, int l, int r, int &rt){ rt=++tot; sum[rt]=sum[p]+1; lc[rt]=lc[p]; rc[rt]=rc[p]; if (l==r) return ; int m=l+r>>1; if (v<=m) modify(lc[p],v,l,m,lc[rt]); else modify(rc[p],v,m+1,r,rc[rt]); } int ask( int L, int R, int l, int r, int k){ if (l==r) return l; int cnt=sum[lc[R]]-sum[lc[L]],m=l+r>>1; if (k<=cnt) return ask(lc[L],lc[R],l,m,k); else return ask(rc[L],rc[R],m+1,r,k-cnt); } int main(){ read(n); memset (h,-1, sizeof (h)); for ( int i=1;i<=n;i++) read(w[i]),bin[i]=w[i]; sort(bin+1,bin+n+1); int x,y; for ( int i=1;i<=n;i++){ w[i]=lower_bound(bin+1,bin+n+1,w[i])-bin;id[w[i]]=i;} for ( int i=1;i<n;i++) read(x),read(y),ae(x,y); dfs(1,0); build(1,n,T[0]); for ( int i=1;i<=n;i++) modify(T[i-1],num[i],1,n,T[i]); int q; read(q); while (q--){ read(x);read(y); printf ( "%d\n" ,id[ask(T[begin[x]-1],T[end[x]],1,n,y)]); } return 0; } |