Discription
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
解题报告
这道题一看是一个数据结构的题,显然是一个单点修改+区间第k大。能够支持这种操作的似乎比较常用的有主席树和分块两种数据结构。我就用好写好调的分块。
这里要注意,我们要找区间第k大,然而分块只能用二分查找(upper_bound)找到大于某个数的位置,不能直接找到这个数。怎么办呢?我们就想到了二分查找,用二分的方式枚举答案,找他在有序数列中的位置是不是这个k就行了。
代码如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=50000;
const int M=10000;
int T;
int n,m,blk,cnt;
int a[N+5],b[N+5],b1[N+5],L[N+5],R[N+5];
void reset(int x)
{
for(int i=L[x];i<=R[x];i++)b[i]=a[i];
sort(b+L[x],b+1+R[x]);
}
void modify(int pos,int val)
{
a[pos]=val;
reset(b1[pos]);
}
int query(int l,int r,int k)
{
int lf=b1[l],rg=b1[r];
int ans=0;
if(lf==rg)//灾一个块中直接暴力
{
for(int i=l;i<=r;i++)if(a[i]<=k)ans++;
}
else
{
for(int i=l;i<=R[lf];i++)if(a[i]<=k)ans++;
for(int i=L[rg];i<=r;i++)if(a[i]<=k)ans++;//左右不完全在一个块里的零头,暴力枚举
for(int i=lf+1;i<=rg-1;i++)
ans+=upper_bound(b+L[i],b+R[i]+1,k)-(b+L[i]);//比k大的灾块中的具体排名
}
return ans;
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
blk=sqrt(n);
if(n%blk)cnt=n/blk+1;
else cnt=n/blk;
for(int i=1;i<=n;i++)b1[i]=(i-1)/blk+1;//i所属的那个块的序号
for(int i=1;i<=cnt;i++)L[i]=(i-1)*blk+1,R[i]=i*blk;//一个块的左右下标
R[cnt]=n;
for(int i=1;i<=cnt;i++)reset(i);
char ch;
while(m--)
{
scanf(" %c",&ch);
if(ch=='Q')
{
int lf,rg,k;
scanf("%d%d%d",&lf,&rg,&k);
int l=1,r=1e9,res;
while(l<=r)//二分答案
{
int mid=(l+r)>>1;
if(query(lf,rg,mid)>=k)r=mid-1,res=mid;
else l=mid+1;
}
printf("%d\n",res);
}
else
{
int pos,val;
scanf("%d%d",&pos,&val);
modify(pos,val);
}
}
}
return 0;
}