zoj 2112 or bzoj1901 分析动态区间第k大的几种解法。
The Company Dynamic Rankings has developed a new kind of computer that is no longer satisfied with the query like to simply find the k-th smallest number of the given N numbers. They have developed a more powerful system such that for N numbers a[1], a[2], ..., a[N], you can ask it like: what is the k-th smallest number of a[i], a[i+1], ..., a[j]? (For some i<=j, 0<k<=j+1-i that you have given to it). More powerful, you can even change the value of some a[i], and continue to query, all the same.
Your task is to write a program for this computer, which
- Reads N numbers from the input (1 <= N <= 50,000)
- Processes M instructions of the input (1 <= M <= 10,000). These instructions include querying the k-th smallest number of a[i], a[i+1], ..., a[j] and change some a[i] to t.
Input
The first line of the input is a single number X (0 < X <= 4), the number of the test cases of the input. Then X blocks each represent a single test case.
The first line of each block contains two integers N and M, representing N numbers and M instruction. It is followed by N lines. The (i+1)-th line represents the number a[i]. Then M lines that is in the following format
Q i j k or
C i t
It represents to query the k-th number of a[i], a[i+1], ..., a[j] and change some a[i] to t, respectively. It is guaranteed that at any time of the operation. Any number a[i] is a non-negative integer that is less than 1,000,000,000.
There're NO breakline between two continuous test cases.
<b< dd="">
Output
For each querying operation, output one integer to represent the result. (i.e. the k-th smallest number of a[i], a[i+1],..., a[j])
There're NO breakline between two continuous test cases.
<b< dd="">
Sample Input
2
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3
<b< dd="">
Sample Output
3
6
3
6
方法一: 分块方法。 分块的时间复杂度是$O(m*logn(sz+n/sz*logn))$,sz就是每块的大小。
我们维持块内有序,然后不是完整的块就暴力查询,这里我sz用了600,好像理论上最好应该是900 。
在zoj上跑了1200ms。
1 #include<bits/stdc++.h> 2 using namespace std; 3 int const N=50000+10; 4 int const sz=600; 5 #define st(x) ((x-1)*sz+1) 6 #define ed(x) min(n,(x*sz)) 7 #define bl(x) (x-1)/sz+1 8 int n,m,a[N],num,b[2003],ta[N]; 9 int query(int x,int y,int z){ 10 int t1=bl(x); 11 int t2=bl(y); 12 int l=0,r=1e9; 13 while (l<r){ 14 int mid=(l+r)/2; 15 int cnt=0; 16 if(t1==t2){ 17 for(int i=x;i<=y;i++) 18 if(ta[i]<=mid) cnt++; 19 }else { 20 for(int i=x;i<=ed(t1);i++) 21 if(ta[i]<=mid) cnt++; 22 for(int i=st(t2);i<=y;i++) 23 if(ta[i]<=mid) cnt++; 24 for(int i=t1+1;i<=t2-1;i++){ 25 int s=st(i); 26 int t=ed(i); 27 if(a[t]<=mid) cnt+=t-s+1; 28 else cnt+=upper_bound(a+s,a+t+1,mid)-(a+s); 29 } 30 } 31 if(cnt>=z) r=mid; 32 else l=mid+1; 33 } 34 return l; 35 } 36 void modify(int x,int y){ 37 int t=bl(x); 38 b[0]=0; 39 int v=ta[x],cnt=1; 40 ta[x]=y; 41 for(int i=st(t);i<=ed(t);i++) 42 if(a[i]!=v || !cnt) b[++b[0]]=a[i]; 43 else cnt--; 44 int k=ed(t)-st(t); 45 while (k && b[k]>y){ 46 b[k+1]=b[k]; 47 k--; 48 } 49 b[k+1]=y; 50 int tot=ed(t)-st(t)+1; 51 for(int i=1;i<=tot;i++) 52 a[st(t)+i-1]=b[i]; 53 } 54 int main(){ 55 int cas; 56 scanf("%d",&cas); 57 while (cas--){ 58 scanf("%d%d",&n,&m); 59 for(int i=1;i<=n;i++) 60 scanf("%d",&a[i]),ta[i]=a[i]; 61 num=(n-1)/sz+1; 62 for(int i=1;i<=num;i++){ 63 int x=st(i); 64 int y=ed(i); 65 sort(a+x,a+y+1); 66 } 67 while (m--){ 68 char s[2]; 69 int x,y,z; 70 scanf("%s%d%d",s,&x,&y); 71 if(s[0]=='C'){ 72 modify(x,y); 73 }else { 74 scanf("%d",&z); 75 printf("%d\n",query(x,y,z)); 76 } 77 } 78 } 79 return 0; 80 }
方法二:
线段树/树状数组套平衡树 O(n*log^3n) 700+ms sqrt(n)和logn还是差的挺远的
每个区间节点维护的是一棵平衡树,平衡树维护的是对应区间内的所有数。
查询的时候同样是二分答案mid,找到[l,r]对应所有区间节点,在每个节点的平衡树上查询<=mid的数有多少个,复杂度二分+线段树+平衡树=O(logn*logn*logn)。
修改的时候找到对应区间删除原来的数,再插入新的数即可 O(logn*logn)。
这种做法满足区间和,所以线段树可以用树状数组替换会比较快。
然后我这样写了为什么比之前的分块还要慢,虽然我自己对拍我的程序还是很快的。
难道我平衡树写了替罪羊树的原因么。 orz。。。
1 #include<bits/stdc++.h> 2 using namespace std ; 3 int const N=60000+10; 4 int const M=1000000+10; 5 int n,m,r[N],sz[M],lc[M],rc[M],sum,val[M],tot[M],cnt[M],top,s[N],a[N]; 6 void update(int x){ 7 sz[x]=sz[lc[x]]+sz[rc[x]]+1; 8 tot[x]=cnt[x]+tot[lc[x]]+tot[rc[x]]; 9 } 10 void dfs(int x){ 11 if(!x) return; 12 dfs(lc[x]); 13 if(cnt[x]) s[++top]=x; 14 dfs(rc[x]); 15 } 16 int build(int l,int r){ 17 if(l>r) return 0; 18 int x=s[(l+r)/2]; 19 if(l==r){ 20 sz[x]=1;tot[x]=cnt[x]; lc[x]=rc[x]=0; 21 return x; 22 } 23 int mid=(l+r)/2; 24 lc[x]=build(l,mid-1); 25 rc[x]=build(mid+1,r); 26 update(x); 27 return x; 28 } 29 void rebuild(int &x){ 30 top=0; 31 dfs(x); 32 x=build(1,top); 33 } 34 void ins(int &x,int v){ 35 if(!x) { 36 x=++sum;sz[x]=1; lc[x]=rc[x]=0;val[x]=v; tot[x]=cnt[x]=1; 37 return; 38 } 39 if(val[x]==v) { 40 cnt[x]++; 41 }else { 42 if(v<val[x]) ins(lc[x],v); 43 else ins(rc[x],v); 44 } 45 update(x); 46 if(sz[x]*0.75<max(sz[lc[x]],sz[rc[x]])) rebuild(x); 47 } 48 49 void dl(int &x,int v){ 50 if(val[x]==v) { 51 cnt[x]--; 52 }else if(v<val[x]) dl(lc[x],v); 53 else dl(rc[x],v); 54 update(x); 55 } 56 void add(int x,int v){for(; x<=n; x+=x&-x) ins(r[x],v);} 57 void del(int x,int v){for(; x<=n; x+=x&-x) dl(r[x],v);} 58 int count(int x,int mid){ 59 if(!x) return 0; 60 int ret; 61 if(val[x]<=mid) ret=tot[lc[x]]+cnt[x]+count(rc[x],mid); 62 else ret=count(lc[x],mid); 63 return ret; 64 } 65 int calc(int x,int mid){ 66 int num=0; 67 for(; x; x-=x&-x) num+=count(r[x],mid); 68 return num; 69 } 70 int solve(int x,int y,int z){ 71 int l=0,r=1e9; 72 while (l<r){ 73 int mid=(l+r)/2; 74 int num=calc(y,mid)-calc(x-1,mid); 75 if(num>=z) r=mid; 76 else l=mid+1; 77 } 78 return r; 79 } 80 int main(){ 81 int cas; 82 scanf("%d",&cas); 83 while (cas--){ 84 scanf("%d%d",&n,&m); 85 sum=0; 86 memset(r,0,sizeof(r)); 87 for(int i=1;i<=n;i++){ 88 scanf("%d",&a[i]); 89 add(i,a[i]); 90 } 91 while (m--){ 92 char s[2]; 93 scanf("%s",s); 94 if(s[0]=='C'){ 95 int x,y; 96 scanf("%d%d",&x,&y); 97 del(x,a[x]); 98 add(x,a[x]=y); 99 } else { 100 int x,y,z; 101 scanf("%d%d%d",&x,&y,&z); 102 printf("%d\n",solve(x,y,z)); 103 } 104 } 105 } 106 return 0; 107 }
方法三:
3.按值建线段树套平衡树 O(n*log^2n) 500ms
这种做法跟第二种刚好相反,线段树是按值建的,节点[l,r]是指满足值>=l && <=r。平衡树维护的是数对应的数组下标。先将所有可能用到的值(包含初始值和修改的值)读入,然后离散化建立线段树,对于a[i](离散化后的),找到所有包含a[i]也就是l<=a[i]<=r的区间节点,将i插入到对应的平衡树中。
查询[ll,rr]的第k大的时候,对于线段树区间[l,r],先查询左孩子[l,mid]中的平衡树查找在[ll,rr]范围内的数有cnt个,假如cnt<=k,则直接递归到左孩子。cnt>k,递归到右孩子查询第k-cnt大(因为左孩子已经包含最小的cnt个了)。思想相当于整体二分吧,复杂度O(logn*logn)。
修改和2类似,复杂度O(logn*logn)
1 #include<bits/stdc++.h> 2 using namespace std; 3 int const N=20000+10; 4 int const M=300000+10; 5 #define lc (x<<1) 6 #define rc (x<<1|1) 7 #define mid (l+r>>1) 8 struct query{ 9 int k,x,y,z; 10 }q[N]; 11 int n,m,a[N],b[N],sz[M],ls[M],rs[M],cnt[M],tot[M],sum,val[M],s[N],top,rt[N<<2]; 12 void update(int x){ 13 sz[x]=1+sz[ls[x]]+sz[rs[x]]; 14 tot[x]=cnt[x]+tot[ls[x]]+tot[rs[x]]; 15 } 16 void dfs(int x){ 17 if(!x) return; 18 dfs(ls[x]) ; 19 if(cnt[x]) s[++top]=x; 20 dfs(rs[x]); 21 } 22 int build(int l,int r){ 23 if(l>r) return 0; 24 int x=s[mid]; 25 if(l==r){ 26 sz[x]=1;tot[x]=cnt[x]; ls[x]=rs[x]=0; 27 return x; 28 } 29 ls[x]=build(l,mid-1); 30 rs[x]=build(mid+1,r); 31 update(x); 32 return x; 33 } 34 35 void rebuild(int &x){ 36 top=0; 37 dfs(x); 38 x=build(1,top); 39 } 40 void ins(int &x,int v){ 41 if(!x) { 42 x=++sum;sz[x]=cnt[x]=tot[x]=1; ls[x]=rs[x]=0; val[x]=v; 43 return; 44 } 45 if(val[x]==v) cnt[x]++; 46 else if(v<val[x]) ins(ls[x],v); 47 else ins(rs[x],v); 48 update(x); 49 if(sz[x]*0.75<max(sz[ls[x]],sz[rs[x]])) 50 rebuild(x); 51 } 52 void insert(int x,int l,int r,int p,int v){ 53 ins(rt[x],v); 54 if(l==r) return; 55 if(p<=mid) insert(lc,l,mid,p,v); 56 else insert(rc,mid+1,r,p,v); 57 } 58 void dl(int &x,int v){ 59 if(val[x]==v) cnt[x]--; 60 else if(v<val[x]) dl(ls[x],v); 61 else dl(rs[x],v); 62 update(x); 63 } 64 void del(int x,int l,int r,int p,int v){ 65 dl(rt[x],v); 66 if(l==r) return; 67 if(p<=mid) del(lc,l,mid,p,v); 68 else del(rc,mid+1,r,p,v); 69 } 70 int count1(int x,int v){ 71 if(!x) return 0; 72 int ret; 73 if(val[x]>=v) ret=count1(ls[x],v)+tot[rs[x]]+cnt[x]; 74 else ret=count1(rs[x],v); 75 return ret; 76 } 77 int count2(int x,int v){ 78 if(!x) return 0; 79 int ret; 80 if(val[x]<=v) ret=count2(rs[x],v)+tot[ls[x]]+cnt[x]; 81 else ret=count2(ls[x],v); 82 return ret; 83 } 84 int query(int x,int l,int r,int ll,int rr,int z){ 85 if(l>r) return 0; 86 if(l==r) return b[r]; 87 int t1=count1(rt[lc],ll); 88 int t2=count2(rt[lc],rr); 89 int s=tot[rt[lc]]; 90 int t=t2-(s-t1); 91 if(t>=z) return query(lc,l,mid,ll,rr,z); 92 else return query(rc,mid+1,r,ll,rr,z-t); 93 } 94 int main(){ 95 int cas=1; 96 // scanf("%d",&cas); 97 while (cas--){ 98 scanf("%d%d",&n,&m); 99 int t=n; 100 for(int i=1;i<=n;i++) 101 scanf("%d",&a[i]),b[i]=a[i]; 102 for(int i=1;i<=m;i++){ 103 char s[2]; 104 scanf("%s",s); 105 if(s[0]=='C') { 106 q[i].k=0; 107 scanf("%d%d",&q[i].x,&q[i].y); 108 b[++t]=q[i].y; 109 }else { 110 q[i].k=1; 111 scanf("%d%d%d",&q[i].x,&q[i].y,&q[i].z); 112 } 113 } 114 sort(b+1,b+t+1); 115 t=unique(b+1,b+t+1)-b-1; 116 for(int i=1;i<=n;i++){ 117 int k=lower_bound(b+1,b+t+1,a[i])-b; 118 insert(1,1,t,k,i); 119 } 120 for(int i=1;i<=m;i++){ 121 if(q[i].k==0) { 122 int p=lower_bound(b+1,b+t+1,a[q[i].x])-b; 123 del(1,1,t,p,q[i].x); 124 a[q[i].x]=q[i].y; 125 p=lower_bound(b+1,b+t+1,a[q[i].x])-b; 126 insert(1,1,t,p,q[i].x); 127 }else { 128 printf("%d\n",query(1,1,t,q[i].x,q[i].y,q[i].z)); 129 } 130 } 131 132 } 133 return 0; 134 135 }