洛谷 P2617 Dynamic Rankings || ZOJ - 2112
写的让人看不懂,仅留作笔记
静态主席树,相当于前缀和套(可持久化方法构建的)值域线段树。
建树方法:记录前缀和的各位置的线段树的root。先建一个"第0棵线段树",是完整的(不需要用可持久化的方法),所有数据为0。后面每一个位置的前缀和放的线段树都先设root与前一位置的线段树一样,然后再按照原序列在指定位置进行(可持久化的)单点加法。(类比放数值的前缀和,每一位置的前缀和是前一位置前缀和加上当前位置数值)
带修改主席树,相当于区间树状数组套(可持久化方法构建的)值域线段树。
建树方法:记录树状数组各位置线段树的root。先建一个"第0棵线段树",是完整的,按普通线段树建,所有数据为0。一开始设所有root都为第0棵线段树的根。对于原序列某位置的值,相当于在树状数组某位置进行一次单点加法。
可持久化方法建线段树:每一个节点进行操作时,都先把原来的节点复制一份(指同时复制左右孩子(以前错在只复制了一个孩子)的位置和数据,但不递归复制左右孩子),再进行更新等操作。这样的话每一次单点更新操作只会新建logn个节点(因为每次单点更新只会路过这么多点),(相比一般的树套树:外层树每一个点放一个完整的内层树)可以节省空间。
由于是值域线段树,都需要做离散化。
这么做,就可以:在log^2n的时间内,完成一次查询"某区间内小于等于某数的数的个数"的操作。(对于区间[l,r],树状数组查询出[1,r]中答案和[1,l-1]中答案,然后相减)
区间第k小:
1.二分答案,log^3n
1 #include<cstdio> 2 #include<algorithm> 3 #include<tr1/unordered_map> 4 #define lowbit(x) ((x)&(-x)) 5 #define mid ((l+r)>>1) 6 using namespace std; 7 using namespace tr1; 8 unordered_map<int,int> ma; 9 int ma2[20010]; 10 struct Q 11 { 12 int t,i,j,x; 13 }q[10010]; 14 int n,m,totn,a[10010],t[20010]; 15 char tmp[10]; 16 int mem,root[20100]; 17 int L,x; 18 int lc[3000100],rc[3000100],dat[3000100]; 19 void build(int l,int r,int& num) 20 { 21 num=mem++; 22 if(l==r){/*dat[num]=0;*/return;} 23 build(l,mid,lc[num]); 24 build(mid+1,r,rc[num]); 25 } 26 void addx(int l,int r,int& num) 27 { 28 int t=num;num=mem++; 29 lc[num]=lc[t];rc[num]=rc[t]; 30 if(l==r) 31 { 32 dat[num]=dat[t]+x; 33 return; 34 } 35 if(L<=mid) addx(l,mid,lc[num]); 36 else addx(mid+1,r,rc[num]); 37 dat[num]=dat[lc[num]]+dat[rc[num]]; 38 } 39 int query(int l,int r,int num)//返回该棵线段树中[1,x]的和,即小于等于x的数的个数 40 { 41 if(l==r) return dat[num]; 42 if(x<=mid) return query(l,mid,lc[num]); 43 else return dat[lc[num]]+query(mid+1,r,rc[num]); 44 } 45 void add(int x){while(x<=n){addx(1,totn,root[x]);x+=lowbit(x);}} 46 int sum1(int x){int ans=0;while(x>0){ans+=query(1,totn,root[x]);x-=lowbit(x);}return ans;} 47 int sum(int l,int r){return sum1(r)-sum1(l-1);} 48 //返回给定x的情况下,[l,r]内小于等于x的数的个数 49 int main() 50 { 51 int i,l,r; 52 scanf("%d%d",&n,&m); 53 for(i=1;i<=n;i++) 54 { 55 scanf("%d",&a[i]); 56 t[++t[0]]=a[i]; 57 } 58 for(i=1;i<=m;i++) 59 { 60 scanf("%s",tmp); 61 if(tmp[0]=='Q') 62 { 63 scanf("%d%d%d",&q[i].i,&q[i].j,&q[i].x); 64 //q[i].t=0; 65 } 66 else if(tmp[0]=='C') 67 { 68 scanf("%d%d",&q[i].i,&q[i].x); 69 q[i].t=1; 70 t[++t[0]]=q[i].x; 71 } 72 } 73 sort(t+1,t+t[0]+1); 74 //for(i=1;i<=t[0];i++) printf("%d ",t[i]);puts(""); 75 totn=unique(t+1,t+t[0]+1)-t-1;//printf("%da\n",totn); 76 for(i=1;i<=totn;i++) ma[t[i]]=i,ma2[i]=t[i]; 77 for(i=1;i<=n;i++) a[i]=ma[a[i]];//printf("%d ",a[i]);puts(""); 78 for(i=1;i<=m;i++) 79 { 80 if(q[i].t==1) 81 { 82 q[i].x=ma[q[i].x]; 83 //sz[q[i].x]++; 84 } 85 } 86 build(1,totn,root[0]); 87 //for(i=1;i<=n;i++) root[i]=root[0];//dat[i]=dat[0]; 88 for(i=1;i<=n;i++) L=a[i],x=1,add(i); 89 //x=3;printf("%db\n",sum1(4)); 90 for(i=1;i<=m;i++) 91 { 92 if(q[i].t==0) 93 { 94 l=0;r=totn; 95 while(r-l>1) 96 { 97 x=(l+r)>>1; 98 if(sum(q[i].i,q[i].j)>=q[i].x) r=x; 99 else l=x; 100 } 101 printf("%d\n",ma2[r]); 102 } 103 else if(q[i].t==1) 104 { 105 l=0;r=totn; 106 while(r-l>1) 107 { 108 x=(l+r)>>1; 109 if(sum(q[i].i,q[i].i)>=1) r=x; 110 else l=x; 111 } 112 L=r;x=-1;add(q[i].i); 113 L=q[i].x;x=1;add(q[i].i); 114 } 115 } 116 return 0; 117 }
2.直接在线段树上二分,log^2n。可以发现普通的在(值域)线段树上直接二分的做法这里并不能使用,因为外层树是树状数组而不是线段树。需要用(看起来比较奇怪(?)的)写法。
我的方法是:根据树状数组查询[l,r]中"小于等于某数的数的个数"的做法,先提取出与此区间相关的线段树的根节点。那么该区间内小于等于某数的树的个数,就由这些线段树中统计出的一部分的答案相加再减去一部分的答案。而这些线段树的结构是完全一致的,因此可以同步地让这些节点向左/右儿子走。
1 #include<cstdio> 2 #include<algorithm> 3 #include<tr1/unordered_map> 4 #define lowbit(x) ((x)&(-x)) 5 #define mid ((l+r)>>1) 6 using namespace std; 7 using namespace tr1; 8 unordered_map<int,int> ma; 9 int ma2[20010]; 10 struct Q 11 { 12 int t,i,j,x; 13 }q[10010]; 14 int n,m,totn,a[10010],t[20010]; 15 char tmp[10]; 16 int mem,root[20100]; 17 int L,x; 18 int lc[3000100],rc[3000100],dat[3000100]; 19 void build(int l,int r,int& num) 20 { 21 num=mem++; 22 if(l==r){/*dat[num]=0;*/return;} 23 build(l,mid,lc[num]); 24 build(mid+1,r,rc[num]); 25 } 26 void addx(int l,int r,int& num) 27 { 28 int t=num;num=mem++; 29 lc[num]=lc[t];rc[num]=rc[t]; 30 if(l==r) 31 { 32 dat[num]=dat[t]+x; 33 return; 34 } 35 if(L<=mid) addx(l,mid,lc[num]); 36 else addx(mid+1,r,rc[num]); 37 dat[num]=dat[lc[num]]+dat[rc[num]]; 38 } 39 void add(int x){while(x<=n){addx(1,totn,root[x]);x+=lowbit(x);}} 40 int tmpr[2][20]; 41 int query(int L,int R,int k)//返回[L,R]内第k小的数 42 { 43 int tx,l=1,r=totn,now,i; 44 for(tx=R,tmpr[0][0]=0;tx>0;tx-=lowbit(tx)) tmpr[0][++tmpr[0][0]]=root[tx]; 45 for(tx=L-1,tmpr[1][0]=0;tx>0;tx-=lowbit(tx)) tmpr[1][++tmpr[1][0]]=root[tx]; 46 while(l!=r) 47 { 48 now=0; 49 for(i=1;i<=tmpr[0][0];i++) now+=dat[lc[tmpr[0][i]]]; 50 for(i=1;i<=tmpr[1][0];i++) now-=dat[lc[tmpr[1][i]]]; 51 if(now>=k) 52 { 53 r=mid; 54 for(i=1;i<=tmpr[0][0];i++) tmpr[0][i]=lc[tmpr[0][i]]; 55 for(i=1;i<=tmpr[1][0];i++) tmpr[1][i]=lc[tmpr[1][i]]; 56 } 57 else 58 { 59 k-=now;l=mid+1; 60 for(i=1;i<=tmpr[0][0];i++) tmpr[0][i]=rc[tmpr[0][i]]; 61 for(i=1;i<=tmpr[1][0];i++) tmpr[1][i]=rc[tmpr[1][i]]; 62 } 63 } 64 return l; 65 } 66 int main() 67 { 68 int i,l,r; 69 scanf("%d%d",&n,&m); 70 for(i=1;i<=n;i++) 71 { 72 scanf("%d",&a[i]); 73 t[++t[0]]=a[i]; 74 } 75 for(i=1;i<=m;i++) 76 { 77 scanf("%s",tmp); 78 if(tmp[0]=='Q') 79 { 80 scanf("%d%d%d",&q[i].i,&q[i].j,&q[i].x); 81 //q[i].t=0; 82 } 83 else if(tmp[0]=='C') 84 { 85 scanf("%d%d",&q[i].i,&q[i].x); 86 q[i].t=1; 87 t[++t[0]]=q[i].x; 88 } 89 } 90 sort(t+1,t+t[0]+1); 91 //for(i=1;i<=t[0];i++) printf("%d ",t[i]);puts(""); 92 totn=unique(t+1,t+t[0]+1)-t-1; 93 for(i=1;i<=totn;i++) ma[t[i]]=i,ma2[i]=t[i]; 94 for(i=1;i<=n;i++) a[i]=ma[a[i]]; 95 for(i=1;i<=m;i++) 96 { 97 if(q[i].t==1) 98 { 99 q[i].x=ma[q[i].x]; 100 //sz[q[i].x]++; 101 } 102 } 103 build(1,totn,root[0]); 104 //for(i=1;i<=n;i++) root[i]=root[0];//dat[i]=dat[0]; 105 for(i=1;i<=n;i++) L=a[i],x=1,add(i); 106 for(i=1;i<=m;i++) 107 { 108 //printf("%d\n",i); 109 if(q[i].t==0) 110 { 111 printf("%d\n",ma2[query(q[i].i,q[i].j,q[i].x)]); 112 } 113 else if(q[i].t==1) 114 { 115 L=query(q[i].i,q[i].i,1);x=-1;add(q[i].i); 116 L=q[i].x;x=1;add(q[i].i); 117 } 118 } 119 return 0; 120 }
修改:先用区间第k小找出这一个点组成的"区间"的"第1小",然后将这个值的数量减去1。然后再进行加的操作。
(要卡常的话,似乎也可以另开一个数组直接进行修改,然后查找"第1小"用在那个数组中查询代替)
以上代码中大量使用了引用的技巧,还是在别人的代码里看到的,很方便。另外好像(没试过)也可以写成使得函数返回当前节点修改完成后的副本。
如果是洛谷的那道题,这样已经够了。但是zoj那道的空间卡的更紧,而且原数列元素多,操作少,因此可以用一些其他技巧:一开始建一棵静态(前缀和)主席树,另建一棵空的动态主席树。修改就在动态主席树上修改,查询则结合两部分。
不过zoj的空间貌似有点奇怪...算出来lc、rc等应该是要开256万左右的,但是貌似会MLE...改成200万就过了
1 #include<cstdio> 2 #include<algorithm> 3 #include<map> 4 #define lowbit(x) ((x)&(-x)) 5 #define mid ((l+r)>>1) 6 using namespace std; 7 map<int,int> ma; 8 int ma2[60010]; 9 struct Q 10 { 11 int t,i,j,x; 12 }q[10010]; 13 int n,m,totn,a[50010],t[60010]; 14 char tmp[10]; 15 int mem,root[60100],root1[50100]; 16 int L,x; 17 int lc[2000100],rc[2000100],dat[2000100]; 18 void build(int l,int r,int& num) 19 { 20 num=mem++; 21 if(l==r){dat[num]=0;return;} 22 build(l,mid,lc[num]); 23 build(mid+1,r,rc[num]); 24 dat[num]=0; 25 } 26 void addx(int l,int r,int& num) 27 { 28 int t=num;num=mem++; 29 lc[num]=lc[t];rc[num]=rc[t]; 30 if(l==r) 31 { 32 dat[num]=dat[t]+x; 33 return; 34 } 35 if(L<=mid) addx(l,mid,lc[num]); 36 else addx(mid+1,r,rc[num]); 37 dat[num]=dat[lc[num]]+dat[rc[num]]; 38 } 39 void add(int x){while(x<=n){addx(1,totn,root[x]);x+=lowbit(x);}} 40 int tmpr[2][20],tmpx[2]; 41 int query(int L,int R,int k)//返回[L,R]内第k小的数 42 { 43 int tx,l=1,r=totn,now,i; 44 for(tx=R,tmpr[0][0]=0;tx>0;tx-=lowbit(tx)) tmpr[0][++tmpr[0][0]]=root[tx]; 45 for(tx=L-1,tmpr[1][0]=0;tx>0;tx-=lowbit(tx)) tmpr[1][++tmpr[1][0]]=root[tx]; 46 tmpx[0]=root1[R];tmpx[1]=root1[L-1]; 47 while(l!=r) 48 { 49 now=0; 50 for(i=1;i<=tmpr[0][0];i++) now+=dat[lc[tmpr[0][i]]]; 51 for(i=1;i<=tmpr[1][0];i++) now-=dat[lc[tmpr[1][i]]]; 52 now+=dat[lc[tmpx[0]]];now-=dat[lc[tmpx[1]]]; 53 if(now>=k) 54 { 55 r=mid; 56 for(i=1;i<=tmpr[0][0];i++) tmpr[0][i]=lc[tmpr[0][i]]; 57 for(i=1;i<=tmpr[1][0];i++) tmpr[1][i]=lc[tmpr[1][i]]; 58 tmpx[0]=lc[tmpx[0]];tmpx[1]=lc[tmpx[1]]; 59 } 60 else 61 { 62 k-=now;l=mid+1; 63 for(i=1;i<=tmpr[0][0];i++) tmpr[0][i]=rc[tmpr[0][i]]; 64 for(i=1;i<=tmpr[1][0];i++) tmpr[1][i]=rc[tmpr[1][i]]; 65 tmpx[0]=rc[tmpx[0]];tmpx[1]=rc[tmpx[1]]; 66 } 67 } 68 return l; 69 } 70 int main() 71 { 72 int i,T; 73 scanf("%d",&T); 74 while(T--) 75 { 76 mem=t[0]=0;ma.clear(); 77 scanf("%d%d",&n,&m); 78 for(i=1;i<=n;i++) 79 { 80 scanf("%d",&a[i]); 81 t[++t[0]]=a[i]; 82 } 83 for(i=1;i<=m;i++) 84 { 85 scanf("%s",tmp); 86 if(tmp[0]=='Q') 87 { 88 scanf("%d%d%d",&q[i].i,&q[i].j,&q[i].x); 89 q[i].t=0; 90 } 91 else if(tmp[0]=='C') 92 { 93 scanf("%d%d",&q[i].i,&q[i].x); 94 q[i].t=1; 95 t[++t[0]]=q[i].x; 96 } 97 } 98 sort(t+1,t+t[0]+1); 99 //for(i=1;i<=t[0];i++) printf("%d ",t[i]);puts(""); 100 totn=unique(t+1,t+t[0]+1)-t-1; 101 for(i=1;i<=totn;i++) ma[t[i]]=i,ma2[i]=t[i]; 102 for(i=1;i<=n;i++) a[i]=ma[a[i]]; 103 for(i=1;i<=m;i++) 104 { 105 if(q[i].t==1) 106 { 107 q[i].x=ma[q[i].x]; 108 //sz[q[i].x]++; 109 } 110 } 111 build(1,totn,root[0]);root1[0]=root[0]; 112 for(i=1;i<=n;i++) root1[i]=root1[i-1],L=a[i],x=1,addx(1,totn,root1[i]); 113 for(i=1;i<=n;i++) root[i]=root[0];//dat[i]=dat[0]; 114 //for(i=1;i<=n;i++) L=a[i],x=1,add(i); 115 for(i=1;i<=m;i++) 116 { 117 if(q[i].t==0) 118 printf("%d\n",ma2[query(q[i].i,q[i].j,q[i].x)]); 119 else if(q[i].t==1) 120 { 121 L=query(q[i].i,q[i].i,1);x=-1;add(q[i].i); 122 L=q[i].x;x=1;add(q[i].i); 123 } 124 } 125 } 126 return 0; 127 }
upd:动态区间第k大(单点修改)不需要主席树,不需要可持久化,只需要普通的树状数组套线段树,内层动态开点即可
1 #include<cstdio> 2 #include<algorithm> 3 #define lowbit(x) ((x)&(-x)) 4 #define mid ((l+r)>>1) 5 using namespace std; 6 const int totn=1e9; 7 struct Q 8 { 9 int t,i,j,x; 10 }q[10010]; 11 int n,m,a[10010]; 12 char tmp[10]; 13 int mem,root[20100]; 14 int L,x; 15 int lc[5000100],rc[5000100],dat[5000100]; 16 void addx(int l,int r,int& num) 17 { 18 if(!num) num=++mem; 19 if(l==r) 20 { 21 dat[num]+=x; 22 return; 23 } 24 if(L<=mid) addx(l,mid,lc[num]); 25 else addx(mid+1,r,rc[num]); 26 dat[num]=dat[lc[num]]+dat[rc[num]]; 27 } 28 void add(int x){while(x<=n){addx(1,totn,root[x]);x+=lowbit(x);}} 29 int tmpr[2][20]; 30 int query(int L,int R,int k)//返回[L,R]内第k小的数 31 { 32 int tx,l=1,r=totn,now,i; 33 for(tx=R,tmpr[0][0]=0;tx>0;tx-=lowbit(tx)) tmpr[0][++tmpr[0][0]]=root[tx]; 34 for(tx=L-1,tmpr[1][0]=0;tx>0;tx-=lowbit(tx)) tmpr[1][++tmpr[1][0]]=root[tx]; 35 while(l!=r) 36 { 37 now=0; 38 for(i=1;i<=tmpr[0][0];i++) now+=dat[lc[tmpr[0][i]]]; 39 for(i=1;i<=tmpr[1][0];i++) now-=dat[lc[tmpr[1][i]]]; 40 if(now>=k) 41 { 42 r=mid; 43 for(i=1;i<=tmpr[0][0];i++) tmpr[0][i]=lc[tmpr[0][i]]; 44 for(i=1;i<=tmpr[1][0];i++) tmpr[1][i]=lc[tmpr[1][i]]; 45 } 46 else 47 { 48 k-=now;l=mid+1; 49 for(i=1;i<=tmpr[0][0];i++) tmpr[0][i]=rc[tmpr[0][i]]; 50 for(i=1;i<=tmpr[1][0];i++) tmpr[1][i]=rc[tmpr[1][i]]; 51 } 52 } 53 return l; 54 } 55 int main() 56 { 57 int i,l,r; 58 scanf("%d%d",&n,&m); 59 for(i=1;i<=n;i++) 60 { 61 scanf("%d",&a[i]); 62 } 63 for(i=1;i<=m;i++) 64 { 65 scanf("%s",tmp); 66 if(tmp[0]=='Q') 67 { 68 scanf("%d%d%d",&q[i].i,&q[i].j,&q[i].x); 69 //q[i].t=0; 70 } 71 else if(tmp[0]=='C') 72 { 73 scanf("%d%d",&q[i].i,&q[i].x); 74 q[i].t=1; 75 } 76 } 77 for(i=1;i<=n;i++) L=a[i],x=1,add(i); 78 for(i=1;i<=m;i++) 79 { 80 //printf("%d\n",i); 81 if(q[i].t==0) 82 { 83 printf("%d\n",query(q[i].i,q[i].j,q[i].x)); 84 } 85 else if(q[i].t==1) 86 { 87 L=query(q[i].i,q[i].i,1);x=-1;add(q[i].i); 88 L=q[i].x;x=1;add(q[i].i); 89 } 90 } 91 return 0; 92 }
upd:整体二分做此题(洛谷交的):
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 using namespace std; 5 struct Q 6 { 7 int type,dat; 8 int pos,fl; 9 int l,r,num; 10 }q[100100],qt1[100100],qt2[100100]; 11 int len,n,m,T; 12 int qnum,ans[101000]; 13 int x[50100]; 14 #define lowbit(x) ((x)&(-x)) 15 struct BIT 16 { 17 int dat[50100]; 18 void addx(int pos,int x){for(;pos<=n;pos+=lowbit(pos))dat[pos]+=x;} 19 int query(int pos){int ans=0;for(;pos>0;pos-=lowbit(pos))ans+=dat[pos];return ans;} 20 }tt; 21 //tt的第i位记录这一位是否小于等于mid 22 void work(int lp,int rp,int l,int r) 23 //在work之前,保证q[lp..rp]中询问都仅计算了所有小于l的修改操作的贡献,q[lp..rp]中不存在小于l的修改操作 24 { 25 if(lp>rp) return;//不可少 26 int i; 27 if(l==r) 28 { 29 for(i=lp;i<=rp;i++) 30 if(q[i].type==2) 31 ans[q[i].num]=l; 32 return; 33 } 34 //tlen1=tlen2=0; 35 int tlen1=0,tlen2=0; 36 int mid=l+((r-l)>>1),t; 37 for(i=lp;i<=rp;i++) 38 { 39 if(q[i].type==1) 40 { 41 if(q[i].dat<=mid) 42 { 43 tt.addx(q[i].pos,q[i].fl); 44 qt1[++tlen1]=q[i]; 45 } 46 else 47 qt2[++tlen2]=q[i]; 48 } 49 else if(q[i].type==2) 50 { 51 t=tt.query(q[i].r)-tt.query(q[i].l-1); 52 /*if(q[i].dat>=t) 53 qt2[++tlen2]=q[i],qt2[tlen2].dat-=t; 54 else 55 qt1[++tlen1]=q[i];*/ 56 if(q[i].dat<=t) 57 qt1[++tlen1]=q[i]; 58 else 59 qt2[++tlen2]=q[i],qt2[tlen2].dat-=t; 60 } 61 } 62 for(i=lp;i<=rp;i++) 63 { 64 if(q[i].type==1) 65 { 66 if(q[i].dat<=mid) 67 tt.addx(q[i].pos,-q[i].fl); 68 } 69 } 70 //不可能每一次都暴力重置整个树状数组,又必须重置,只能把操作反着做一遍 71 memcpy(q+lp,qt1+1,sizeof(Q)*tlen1); 72 memcpy(q+lp+tlen1,qt2+1,sizeof(Q)*tlen2); 73 work(lp,lp+tlen1-1,l,mid); 74 work(lp+tlen1,rp,mid+1,r);//如果不用临时变量作为tlen,执行到上一行后tlen就会改变,导致错误 75 } 76 int main() 77 { 78 int i,a,b,c;char tmp[10]; 79 scanf("%d%d",&n,&m); 80 len=qnum=0; 81 for(i=1;i<=n;i++) 82 { 83 scanf("%d",&x[i]); 84 q[++len].type=1;q[len].pos=i;q[len].dat=x[i];q[len].fl=1; 85 } 86 for(i=1;i<=m;i++) 87 { 88 scanf("%s",tmp); 89 if(tmp[0]=='Q') 90 { 91 scanf("%d%d%d",&a,&b,&c); 92 q[++len].type=2;q[len].l=a;q[len].r=b;q[len].dat=c;q[len].num=++qnum; 93 } 94 else if(tmp[0]=='C') 95 { 96 scanf("%d%d",&a,&b); 97 q[++len].type=1;q[len].pos=a;q[len].dat=x[a];q[len].fl=-1; 98 x[a]=b; 99 q[++len].type=1;q[len].pos=a;q[len].dat=x[a];q[len].fl=1; 100 } 101 } 102 work(1,len,0,1000000000); 103 for(i=1;i<=qnum;i++) printf("%d\n",ans[i]); 104 return 0; 105 }
以下仅做笔记
首先事实上带修改区间k小是不满足整体二分的要求的,要做一个小小的变换:将所有信息变为以下三种操作中的一种:给某个位置加上给定值的贡献,给某个位置删去给定值的贡献,查询某个区间的k小。(不知道为什么网上有叫做“插入”和”删除“的,然而整体二分并不能解决带插入区间第k小。。。。说不定有奇怪的方法可以?再说)
想象对于一个待处理的操作队列,以及现有的答案值域区间([l,r]),首先得到[l,r]的mid=l+(r-l)/2,然后对于所有查询操作,计算对其有贡献的(即发生在其之前的)所有值属于[l,mid]的修改操作(当然根据递归过程能保证不会有操作的值小于l的修改操作)对其的贡献(具体的话,由于这个顺序的要求,需要用一个树状数组来维护数组d[i](表示i位置的当前值带来的贡献,每一次”加上贡献“使其值加1,”删除贡献“使其值减1)的前缀和,这样在处理查询操作的时候就可以快速查询区间内有多少的贡献;由于不能暴力清零树状数组,只能把操作反着做一遍来清零)。
然后是分治过程:
顺次处理所有操作,并(不改变相对顺序地)将其分入两个队列。
对于某个查询操作,操作过程非常类似线段树上二分:如果当前答案(当前小于等于mid产生的贡献)小于需要的答案,就说明取mid作为答案还不够,那么将需要的答案减去当前答案,然后归入第二个队列;如果当前答案大于等于需要的答案,就说明取mid作为答案已经够/超出了,那么直接将询问归入第一个队列。(不需要考虑对第k小更加形式化的定义。。。原来想多了)
对于某个修改操作,如果其操作值小于等于mid,那么对第二个队列中查询操作的贡献都已经计算(在上面那个”减去当前答案“的过程中),因此只归入第一个队列;反之,说明其对第一个队列中查询操作不可能产生贡献,因此归入第二个队列。
然后,递归处理第一个队列以及其对应值域区间[l,mid],还有第二个队列及其对应值域区间[mid+1,r]。
当然,在函数中如果发现某一刻操作队列是空的,应该直接退出,不处理;如果发现l==r,说明当前操作队列中询问操作都已经找到正确答案,直接更新答案然后退出即可。
复杂度?任意一个操作只会log(值域)次出现在work函数中,设f(n)为一个函数(。。。。。),如果work函数中对于长度为n的操作序列的额外消耗时间(即除了递归处理以外的时间)是O(n*f(n))的,也就是对于任意一个操作的额外消耗时间是O(f(n))的(此题中f(n)=logn),因此总复杂度是O((n+q)f(n)log(值域))(n是序列长度,q是操作个数)