动态区间第K小数 分块/树套树

来源:互联网 发布:sql 去重 编辑:程序博客网 时间:2024/05/17 04:30

NKOJ2670 动态区间第K小数

问题描述

给定一个由N个数组成的序列{A1,A2,…,AN}
每次可以将Ak的值改为t,或者提问序列中{Al,..,Ar}中第k小的数的值。

输入格式

第一行两个正整数N,M,表示有N个数,M次操作
接下来每行描述一个操作:
·如果第一个字符时C,那么接下来两个正整数k,t,表示把Ak的值改成t;
·如果第一个字符时Q,那么接下来三个正整数l,r,k,表示提问序列中{Al,..,Ar}中第k小的数的值。

输出格式

对每次提问输出一行,为改次提问的答案。

数据范围

1<=N,M<=50000
初始状态1<=Ak<=50000
每次修改1<=k<=N,1<=t<=50000
每次提问1<=k<=r-l+1


可以用树套树解决,但是编程难度相当大。实际上可以用分块暴力水过。

A数组用来处理C操作,初始就是原序列;用来处理Q操作的的B数组分块之后,把每块中的数进行排序。注意到序列中的数最大只有50000,我们可以对区间第K小数二分答案。如果区间左右端点包含于同一个区间,那么暴力扫一遍判断即可;其他情况下,对于完全覆盖的区间,二分查找该区间比该数小的数有几个,对于两端剩下的区间暴力扫一遍判断比它少的有几个,最后加起来判断即可。

对于C操作,修改后对该区间再排序即可。

最坏情况下,时间复杂度好像是O(MNlog2Nlog250000),如有错误请指正。


#include<stdio.h>#include<cmath>#include<algorithm>#define MAXN 50005using namespace std;int N,M,S,A[MAXN],B[MAXN];bool check(int num,int l,int r,int k){    int i,j,x,y,xx,yy,sum=0;    x=(l-1)/S+1;xx=x*S;    y=(r-1)/S+1;yy=(y-1)*S+1;    if(x==y)    {        for(i=l;i<=r;i++)sum+=A[i]<num;        return sum>=k;    }    for(i=l;i<=xx;i++)sum+=A[i]<num;    for(i=yy;i<=r;i++)sum+=A[i]<num;    for(i=x+1;i<y;i++)sum+=lower_bound(B+(i-1)*S+1,B+i*S+1,num)-B-((i-1)*S+1);    return sum>=k;}int GetAns(int l,int r,int k){    int L=0,R=50001,mid;    while(L<R-1)    {        mid=L+R>>1;        if(check(mid,l,r,k))R=mid;        else L=mid;    }    return L;}void Modify(int x,int d){    int i,j,l,r;    A[x]=d;    j=(x-1)/S+1;l=(j-1)*S+1;r=j*S;    if(r>N)r=N;    for(i=l;i<=r;i++)B[i]=A[i];    sort(B+l,B+r+1);}int main(){    int i,j,x,y,z;    char op[3];    scanf("%d%d",&N,&M);    for(i=1;i<=N;i++)scanf("%d",&A[i]),B[i]=A[i];    S=sqrt(N);    j=N/S;    for(i=1;i<=j;i++)sort(B+(i-1)*S+1,B+i*S+1);    if(N%S)sort(B+j*S+1,B+N+1);    for(i=1;i<=M;i++)    {        scanf("%s",op);        if(op[0]=='Q')        {            scanf("%d%d%d",&x,&y,&z);            printf("%d\n",GetAns(x,y,z));        }        else        {            scanf("%d%d",&x,&y);            Modify(x,y);        }    }}
原创粉丝点击