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.
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.
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
Sample Output
3
6
3
6
题目大意:给出多组数据,对于每组数据,先读入两个值N,M,表示一个N个数的数列,有M次操作。
接下来一行读入数列的值。对于操作分为两种C i j 表示把数列中第I个数的值改为j, Q i j k 表示求区间[I,J]的第k小。
题解这道题用到了树套树的思想。
通常的求区间第K小,都是用主席树维护的。但是这里引入了修改操作。如果只是单纯的使用主席树,那么每此修改都需要遍历一遍序列,重新建立主席树时间复杂度应该是o(nlogn),那如果重建M 次0(mnlogn)赤裸裸的超时了。
那么这时就需要用树状数组来优化。
考虑到前缀和,我们通过树状数组来优化,即树状数组套主席树, 每个节点都对应一棵主席树,那么修改操作就只要修改logn棵树, o(nlognlogn+Mlognlogn)时间是可以的, 但是直接建树要nlogn*logn(10^7)会MLE 我们发现对于静态的建树我们只要nlogn个节点就可以了, 而且对于修改操作,只是修改M次,每次改变俩个值(减去原先的,加上现在的) 也就是说如果把所有初值都插入到树状数组里是不值得的, 所以我们分两部分来做,所有初值按照静态来建,内存O(nlogn), 而修改部分保存在树状数组中,每次修改logn棵树,每次插入增加logn个节点 O(M*logn*logn+nlogn),其实说白了,树状数组的作用维护了1到这个点的前缀和,我们对于每一次修改都是在树状数组相应的节点中修改的,那么在最终查询之时最多需要logn各点就可以覆盖整个区间,所以这样就大大减小了我们查询修改所需要的时间,如果还是不理解,那么就结合程序感性的理解一下。
建议先了解一下动态数组的应用,否则有些难以理解。
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> #include<vector> #define l(i) t[i].l #define r(i) t[i].r #define w(i) t[i].w using namespace std; const int N=60100; int n,m,a[N],root[N*2],n1,sz,cnt; vector<int> q1,q2,lx; struct data{ int l,r,w; };data t[2000000]; struct node { int l,r,k,s; };node ans[10010]; int find(int x) { return lower_bound(lx.begin(),lx.begin()+n1,x)-lx.begin()+1;//函数lower_bound()在first和last中的前闭后开区间进行二分查找,返回大于或等于val的第一个元素位置.如果所有元素都小于val,则返回last的位置 } int lowbit(int x) { return x&(-x); } void build(int &i,int l,int r,int x) { t[++sz]=t[i]; i=sz; w(i)++; if (l==r) return; int mid=(l+r)/2; if (x<=mid) build(l(i),l,mid,x); else build(r(i),mid+1,r,x); } void ins(int &i,int l,int r,int x,int v) { if(i==0) t[++sz]=t[i],i=sz; w(i)+=v; if (l==r) return; int mid=(l+r)/2; if (x<=mid) ins(l(i),l,mid,x,v); else ins(r(i),mid+1,r,x,v); } void my_ins(int pos,int x,int v) { int t=find(x); for (int i=pos;i<=n;i+=lowbit(i)) ins(root[i],1,n1,t,v); } int qy(vector<int> q1,vector<int> q2,int l,int r,int k) { if (l==r) return l; int x=0; for (int i=0;i<q1.size();i++) x-=w(l(q1[i])); for (int i=0;i<q2.size();i++) x+=w(l(q2[i])); for (int i=0;i<q1.size();i++) q1[i]=(x>=k?l(q1[i]):r(q1[i])); for (int i=0;i<q2.size();i++) q2[i]=(x>=k?l(q2[i]):r(q2[i])); int mid=(l+r)/2; if(x>=k) return qy(q1,q2,l,mid,k); else return qy(q1,q2,mid+1,r,k-x); } void query(int l,int r,int x) { q1.clear(); q2.clear(); q1.push_back(root[l!=1?l-1+n:0]);//root[0]是一颗空树 q2.push_back(root[r+n]); for (int i=l-1;i>0;i-=lowbit(i)) q1.push_back(root[i]); for (int i=r;i>0;i-=lowbit(i)) q2.push_back(root[i]); int t=qy(q1,q2,1,n1,x); printf("%d\n",lx[t-1]); } void work() { sz=0; memset(root,0,sizeof(root)); for (int i=1;i<=n;i++) { root[i+n]=root[i-1+n]; int t=find(a[i]); build(root[i+n],1,n1,t); }//把数列刚开始的值建成一颗静态的主席树 for (int i=1;i<=m;i++) if (ans[i].s==1) query(ans[i].l,ans[i].r,ans[i].k); else { my_ins(ans[i].l,a[ans[i].l],-1); my_ins(ans[i].l,ans[i].k,1); a[ans[i].l]=ans[i].k; } } int main() { //freopen("a.in","r",stdin); //freopen("my.out","w",stdout); scanf("%d",&cnt); for (int ll=1;ll<=cnt;ll++) { scanf("%d%d",&n,&m); lx.clear(); for (int i=1;i<=n;i++) scanf("%d",&a[i]),lx.push_back(a[i]); for (int i=1;i<=m;i++) { char c[10]; scanf("%s",c); if (c[0]=='C') ans[i].s=2,scanf("%d%d",&ans[i].l,&ans[i].k),lx.push_back(ans[i].k); else ans[i].s=1,scanf("%d%d%d",&ans[i].l,&ans[i].r,&ans[i].k); } sort(lx.begin(),lx.end());//把动态数组中的元素排序 n1=unique(lx.begin(),lx.end())-lx.begin();//unique()函数是将重复的元素折叠缩编,使成唯一,剔除相邻之间字符重复的,unique()函数的返回值是源数组中去除相邻之间相同字符后剩下的字符串中的最后一个字符的下一个位置,注意动态数组是从【0】开始存储的 work(); } }