BZOJ1901 Zju2112 Dynamic Rankings
AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=1901
[分析]
这题是上题:不带修改的求区间第k小的加强版。
让我们再回顾一下上一道题的解法:利用线段树做桶,得知在一定数值区间内的元素的数量。利用前缀和得知在一定下标区间内的元素的数量。
两者结合——建n棵线段树,第i棵线段树表示前i个元素在数值上的分布。利用前缀和和线段树可以知道在确定的下标区间内一定数值区间内有多少数字,然后二分查找找到第k个元素所在的数值区间。
现在我们若是按着上面的思路:仍然是做桶,仍然是利用前缀和相减。但是对某个元素的修改,会影响到其后面所有的前缀和,于是对后面的也都进行一次修改。[可这好像每个操作都是nlogn的...明显太慢]
于是我们想起了一种可以快速修改和获得前缀和的数据结构——树状数组。
每次修改时只需要修改[j+lowbit(j)]等等的元素,每次二分下去时需要对x,y两棵子树分别求前缀和[j-lowbit(j)]等等的和。
这样修改和获取前缀和的复杂度都是logn的,所以每次操作都是(logn)^2的。
再分析一下空间复杂度:
回顾之前,我们虽然建了n棵树但事实上很多部分是相同的,所以那些部分我们都没有管,只是将节点与上一个节点定为相同,然后对于变化的部分再操作。
那么我们这道题如果按照上面的方法大概是不太可行的,因为现在我们每棵线段树表示的是树状数组中的含义[例如6表示5+6,12表示9+10+11+12等等...]也就是说每次修改的前一个是可能不同的,不能将当前节点设为原来的节点改[事实上,原来那种改法是因为前驱只有一个以及只要修改一个设计出来的,而我们这里两个都不满足],但是有一点思想是好的,就是我改变的每次都只有一条链,而每次修改就是添加一条链上去,不是整个线段树已经建好了再去修改[有点类似Trie树的构造——From TB],所以空间上复杂度也不吃紧,(n+m)(logn)也是可以接受的。
上面主要讲了使用什么样的线段树,下面具体说一下怎么使用线段树的问题。
操作0:因为线段树在这里做桶,于是使用Hash来离散化,即预处理快排一下,然后查找的时候二分找对应的位置。[注意:因为操作中有修改元素的操作,所以离散化需要将这些元素也包含在内,这就需要离线处理]
操作1:预处理原序列:对于第i个元素,对i及i后面的[指树状数组中i+lowbit(i)的]所有节点都加上这个元素。
操作2:修改某个位置上的元素的值:首先按原来的值在这个节点及以后的所有节点上-1,然后按新的值在这个节点及以后的所有节点上+1
操作3:询问一段区间内排行第k的元素:和静态类似。但是每次求左右子树的前缀和时使用树桩数组 [因为所有的线段树都是相同的含义,所以将树桩数组中该节点的所有前驱节点的左儿子加起来就是需要的左儿子的总和]。然后二分递归即可。
上代码[有%H的痕迹...]
#include<cstdio> #include<cstring> #include<algorithm> const int maxn=200010; using namespace std; int n,m,tot,top,sz; int v[maxn],num[maxn],hash[maxn]; int A[maxn],B[maxn],K[maxn],rt[maxn]; int L[20],R[20],a,b; char ord[2]; bool flag[maxn]; struct Node{int l,r,sz;}s[maxn*15]; inline int lowbit(int x){return x&(-x);} int find(int x){ int l=1,r=tot,mid; while(l<=r){ int mid=(l+r)>>1; if(hash[mid]<x)l=mid+1; else r=mid-1; } return l; } void update(int last,int l,int r,int &rt,int key,int x){ rt=++sz,s[rt]=s[last];s[rt].sz+=x; if(l==r) return; int mid=(l+r)>>1; if(key<=mid) update(s[last].l,l,mid,s[rt].l,key,x); else update(s[last].r,mid+1,r,s[rt].r,key,x); } int query(int l,int r,int k){ if(l==r) return l; int i,suml=0,sumr=0; for(i=1;i<=a;i++) suml+=s[s[L[i]].l].sz; for(i=1;i<=b;i++) sumr+=s[s[R[i]].l].sz; int mid=(l+r)>>1; if(sumr-suml>=k){ for(i=1;i<=a;i++) L[i]=s[L[i]].l; for(i=1;i<=b;i++) R[i]=s[R[i]].l; return query(l,mid,k); } else{ for(i=1;i<=a;i++) L[i]=s[L[i]].r; for(i=1;i<=b;i++) R[i]=s[R[i]].r; return query(mid+1,r,k-(sumr-suml)); } } int main(){ #ifndef ONLINE_JUDGE freopen("1901.in","r",stdin); freopen("1901.out","w",stdout); #endif scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&v[i]),num[++top]=v[i]; for(int i=1;i<=m;i++){ scanf("%s%d%d",ord,&A[i],&B[i]); if(ord[0]=='Q') scanf("%d",&K[i]),flag[i]=true; else num[++top]=B[i]; } sort(num+1,num+top+1); hash[++tot]=num[1]; for(int i=2;i<=top;i++) if(num[i]!=num[i-1]) hash[++tot]=num[i]; for(int i=1;i<=n;i++){ int t=find(v[i]); for(int j=i;j<=n;j+=lowbit(j)) update(rt[j],1,tot,rt[j],t,1); } for(int i=1;i<=m;i++) if(flag[i]){ a=b=0;A[i]--; for(int j=A[i];j;j-=lowbit(j)) L[++a]=rt[j]; for(int j=B[i];j;j-=lowbit(j)) R[++b]=rt[j]; printf("%d\n",hash[query(1,tot,K[i])]); } else{ int t=find(v[A[i]]); for(int j=A[i];j<=n;j+=lowbit(j)) update(rt[j],1,tot,rt[j],t,-1); v[A[i]]=B[i]; t=find(B[i]); for(int j=A[i];j<=n;j+=lowbit(j)) update(rt[j],1,tot,rt[j],t,1); } return 0; }
后来发现因为这题可以离线操作,所以使用整体二分会很有效果,而且整体二分思想十分带劲?...感觉挺神奇的。
大概就是二分答案与操作分类并行,然后得到结果的一种方法?
复杂度是nlog(n)log(INF),跑得不算慢。
代码觉得看上去会比较清晰,自己可以推一推。
吖,这题好像没有用离散化就过了?!...果然数据减弱了的样子...树状数组开的是maxn,当时一定是没有仔细思考...
不过我懒得改了?...如果要看一下离散化的树状数组做法,就不如看一下它的减弱版吧:http://www.cnblogs.com/Robert-Yuan/p/5102318.html
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; inline int in(){ int x=0;char ch=getchar(); while(ch>'9' || ch<'0') ch=getchar(); while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); return x; } const int maxn=200010; const int INF=0x3f3f3f3f; struct Query{ int x,y,k,type,cur,id; }q[maxn],q1[maxn],q2[maxn]; int n,m,tot,Idex; int a[maxn],ans[maxn]; int b[maxn],tmp[maxn],t[maxn]; void add(int x,int d){ for(int i=x;i<=n;i+=i&-i) t[i]+=d; } int ask(int x){ int sum=0; for(int i=x;i;i-=i&-i) sum+=t[i]; return sum; } void div(int H,int T,int l,int r){ if(H>T) return ; if(l==r){ for(int i=H;i<=T;i++) if(q[i].type==3) ans[q[i].id]=l; return ; } int mid=(l+r)>>1; for(int i=H;i<=T;i++){ if(q[i].type==1 && q[i].y<=mid) add(q[i].x,1); else if(q[i].type==2 && q[i].y<=mid) add(q[i].x,-1); else if(q[i].type==3) tmp[i]=ask(q[i].y)-ask(q[i].x-1); } for(int i=H;i<=T;i++) if(q[i].type==1 && q[i].y<=mid) add(q[i].x,-1); else if(q[i].type==2 && q[i].y<=mid) add(q[i].x,1); int l1=0,l2=0; for(int i=H;i<=T;i++){ if(q[i].type==3){ if(q[i].cur+tmp[i]>=q[i].k) q1[++l1]=q[i]; else q[i].cur+=tmp[i],q2[++l2]=q[i]; } else{ if(q[i].y<=mid) q1[++l1]=q[i]; else q2[++l2]=q[i]; } } for(int i=1;i<=l1;i++) q[H+i-1]=q1[i]; for(int i=1;i<=l2;i++) q[H+l1+i-1]=q2[i]; div(H,H+l1-1,l,mid); div(H+l1,T,mid+1,r); } int main(){ #ifndef ONLINE_JUDGE freopen("1901.in","r",stdin); freopen("1901.out","w",stdout); #endif int x,y; char ord[2]; n=in();m=in(); for(int i=1;i<=n;i++){ a[i]=in(); q[++tot].type=1,q[tot].x=i,q[tot].y=a[i];; } for(int i=1;i<=m;i++){ scanf("%s",ord); if(ord[0]=='Q'){ q[++tot].x=in(),q[tot].y=in(),q[tot].k=in(); q[tot].id=++Idex,q[tot].type=3; } else{ x=in(),y=in(); q[++tot].x=x,q[tot].y=a[x],q[tot].type=2; q[++tot].x=x,q[tot].y=y,q[tot].type=1; a[x]=y; } } div(1,tot,0,INF); for(int i=1;i<=Idex;i++) printf("%d\n",ans[i]); return 0; }