Dynamic Rankings
Time Limit: 10 Seconds Memory Limit: 32768 KB
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
(adviser)
Site: http://zhuzeyuan.hp.infoseek.co.jp/index.files/our_contest_20040619.htm
题目大意:就是有修改的询问区间第k小。
解题思路:没有修改操作的话,有很多种解题方法,划分树,树状数组离线(固定右端点),主席树。
同样,对于带有修改操做,一样能用主席树来解决。
1.按照原来的方法,将询问的点同原数组一起离散化放进主席树里。
2.因为一颗树记录的是数组前缀各个区间的数字个数,我们要更新第i个数字的话我们就要把第i棵树以及以后的树全扫一边然后更新,这样的话会超时。
3.对于修改一个数字(假设把第i个数字i变成了j)我们考虑他的影响,就是当前位置到N所有包括 i 的区间的sum都将减少1,所有包含
j的区间都将加上1.
4.这个类似于区间修改(把i到n包含这个数的区间都减去1,然后把包含更新后的数字的区间都加上1),单点查询,因此我们可以借助树状数组来配合主席树完成。
5.这样修改的话对于原来的查需我们还在原来的静态主席树上进行。
6.当修改时,我们树状数组每一个节点都是一棵树的根节点的值,此时如果我们再更新第i棵树到第n棵树时,我们只需要更新logn
棵树就行了(每棵树更新logn个节点)。
7.最后在查询时,我们要加上树状数组记录的值(对答案的影响)就行了。
注意:这里记录一下需要注意的点,原名我想对于每一个节点建立一颗树的话,这样的话就不用每次update的时候动态开点了(具体见下方代码),后来想了一下我们之所以动态开点不就是为了解决这个问题吗?初始时每个树状数组的节点对应T[0],也就是一颗空树。此外为了查需下推时不出错,我们用两个数组ul,ur记录当前查询树状数组上节点的左右子节点。当查询到下一层时ul和ur也要向下推,就是Go函数中的内容。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define lowb(x) x&-x
#define N 60005
int n,cnt;
struct node
{
int f,l,r,k;
}q[N];
int T[N],L[N*32],R[N*32],sum[N*32];//空间大概是原来的空间加上m*logn*logn
int a[N],b[N],S[N];
int tot;
void build(int &rt,int l,int r)
{
rt=++tot;
sum[rt]=0;
if(l==r)return ;
int m=(l+r)>>1;
build(L[rt],l,m);
build(R[rt],m+1,r);
}
void update(int &rt,int pre,int l,int r,int k,int val)
{
rt=++tot;
L[rt]=L[pre],R[rt]=R[pre];
sum[rt]=sum[pre]+val;
if(l==r)return ;
int m=(l+r)>>1;
if(k<=m) update(L[rt],L[pre],l,m,k,val);
else update(R[rt],R[pre],m+1,r,k,val);
}//建树和更新都与静态主席树一样。
void add(int x,int val)
{
int p=lower_bound(b+1,b+1+cnt,a[x])-b;
while(x<=n)
{
update(S[x],S[x],1,cnt,p,val);
x+=lowb(x);
}
}
//修改第i棵树到第n棵树(区间修改)
int ul[N],ur[N];
void Go(int l,int r,int f)
{
if(!f){
for(int i=l;i;i-=lowb(i))ul[i]=L[ul[i]];
for(int i=r;i;i-=lowb(i))ur[i]=L[ur[i]];
}
if(f){
for(int i=l;i;i-=lowb(i))ul[i]=R[ul[i]];
for(int i=r;i;i-=lowb(i))ur[i]=R[ur[i]];
}
}//因为时动态开点,所以我们要记录当前节点左右子节点的位置。
int SUM(int p,int f)
{
int ans=0;
while(p)
{
if(f)ans+=sum[L[ur[p]]];
else ans+=sum[L[ul[p]]];
p-=lowb(p);
}
return ans;
}
int query(int s,int e,int l,int r,int ql,int qr,int k)
{
if(l==r)return l;
int now=SUM(qr,1)-SUM(ql,0)+sum[L[e]]-sum[L[s]];
int m=(l+r)>>1;
if(k<=now)
{
Go(ql,qr,0);
return query(L[s],L[e],l,m,ql,qr,k);
}
else
{
Go(ql,qr,1);
return query(R[s],R[e],m+1,r,ql,qr,k-now);
}
}
int main()
{
int t;
cin>>t;
while(t--)
{
int m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[i]=a[i];
}
char s[8];
cnt=n;
for(int i=1;i<=m;i++)
{
scanf("%s",s);
if(s[0]=='Q')
{
q[i].f=1;
scanf("%d%d%d",&q[i].l,&q[i].r,&q[i].k);
}
else
{
q[i].f=0;
scanf("%d%d",&q[i].l,&q[i].k);
b[++cnt]=q[i].k;
}
}
sort(b+1,b+1+cnt);
int tmp = unique(b+1,b+1+cnt)-b-1;
cnt=tmp;
tot=0;//重要 tot的初始化
build(T[0],1,cnt);
for(int i=1;i<=n;i++)update(T[i],T[i-1],1,cnt,lower_bound(b+1,b+1+cnt,a[i])-b,1);
for(int i=1;i<=n;i++)S[i]=T[0];//初始时每个都对应一颗空树的根节点。
for(int i=1;i<=m;i++)
{
if(q[i].f)
{
int ql=q[i].l;
int qr=q[i].r;
for(int j=ql-1;j;j-=lowb(j)) ul[j]=S[j];//为树状数组上的节点下推做准备。
for(int j=qr;j;j-=lowb(j)) ur[j]=S[j];
printf("%d\n",b[query(T[ql-1],T[qr],1,cnt,ql-1,qr,q[i].k)]);
}
else
{
add(q[i].l,-1);
a[q[i].l]=q[i].k;//这里要先更新后add
add(q[i].l,1);
//cout<<"del late="<<sum[S[q[i].l]]<<endl;
}
}
}
}