• P3834 【模板】可持久化线段树 2(主席树)题解


    Link

    P3834 【模板】可持久化线段树 2(主席树)

    Solve

    这道题有很多种做法,我用的是离线分治算法

    设序列(A)中最小的数为(MINA),最大的数为(MAXA),我们尝试在值域([MINA,MAXA])上进行二分答案,设第一次二分的值为(mid).

    在二分的过程中,扫描每个询问,统计在下标区间([l,r])中不大于(mid)的数有多少个,记为(c_i),对于每个([l_i,r_i]),(mid)都是一个固定的值,所以可以用树状数组来线性的求出每个(c_i)

    然后我们对询问进行分类:

    1.若(k_i≤c_i)则说明第(i)个询问的答案在([MINA,mid])中。

    2.若(k_i>c_i),则说明第(i)个询问的答案在([mid+1,MAXA])中,并且等价于在值域([mid+1,MAXA])中的限制下找第(k_i-c_i)小的数。

    我们把序列(A)中的数也进行分类,不大于(mid)的数构成一个子序列(LA),大于(mid)的数构成一个子序列(RA)

    于是我们就可以把询问和数值分成两类,并且两类互不相关。

    我们就得到了一个分治算法,设(solve(L,R,a,q)),表示有一个值域为([L,R])的整数序列(a),询问序列(q)(q)中存储个若干个三元组(l_i,r_i,k_i),表示求(a)的下标区间([l_i,r_i])中第(k_i)小的数。

    1.设(mid=(L+R)>>1)

    2.利用树状数组,对于(q)中每个询问,统计在序列(a)下标区间([l_i,r_i])中不大于(mid)的数有多少个,记为(c_i).

    3.若(k_i≤c_i),则把该询问加入到(lq)中,否则令(k_i-=c_i),将其加入到序列(rq)中。

    4.令序列(a)(≤mid)的数构成序列(la)(>mid)的数构成序列(ra)

    5.递归求解(solve(L,mid,la,lq))(solve(mid+1,R,ra,rq))

    递归的边界为:当(q)为空时,直接返回。当(L==R)时,说明询问序列中的每个问题L就是答案(R也一样),统计后返回

    在实际做的时候我们把(lq)(la)放在一个序列里面做,打一个(op)标记判断一下,因为(la,lq)(ra,rq)是互不干扰的。合并序列时直接在原序列上修改,然后记下范围指针就好了

    因为分治最多进行(logSIZE)层((SIZE)为值域大小),在算上树状数组的(log),整个事件复杂度为(O((N+M)logSIZElogN)),如果加上离散化,可以做到(O((N+M)log^2N))

    Code

    #include<bits/stdc++.h>
    using namespace std;
    int N,M,tot;
    const int maxn=200005,INF=1<<30;
    struct AS{
    	int x,y,z,op;
    }q[maxn<<1],lq[maxn<<1],rq[maxn<<1];
    int ans[maxn],c[maxn];
    
    inline int read(){
    	int ret=0,f=1;char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
    	while(ch<='9'&&ch>='0')ret=ret*10+ch-'0',ch=getchar();
    	return ret*f;
    }
    
    inline void add_x(int x,int val){
    	for(int i=x;i<=N;i+=i&-i)c[i]+=val;
    	return ;
    }
    
    inline int get(int x){
    	int ret=0;
    	for(int i=x;i;i-=i&-i)ret+=c[i];
    	return ret;
    }
    
    void solve(int lval,int rval,int st,int ed){
    	if(st>ed)return ;
    	if(lval==rval){
    		for(int i=st;i<=ed;i++){
    			if(q[i].op>0)ans[q[i].op]=lval;
    		}
    		return ;
    	}
    	int mid=lval+rval>>1;
    	int lt=0,rt=0;
    	for(int i=st;i<=ed;i++){
    		if(q[i].op==0){
    			if(q[i].y<=mid)add_x(q[i].x,1),lq[++lt]=q[i];
    			else rq[++rt]=q[i];
    		}
    		else {
    			int cnt=get(q[i].y)-get(q[i].x-1);
    			if(q[i].z<=cnt)lq[++lt]=q[i];
    			else q[i].z-=cnt,rq[++rt]=q[i];
    		}
    	}
    	for(int i=ed;i>=st;i--){
    		if(q[i].op==0&&q[i].y<=mid)add_x(q[i].x,-1);
    	}
    	for(int i=1;i<=lt;i++)q[st+i-1]=lq[i];
    	for(int i=1;i<=rt;i++)q[st+lt+i-1]=rq[i];
    	solve(lval,mid,st,st+lt-1);
    	solve(mid+1,rval,st+lt,ed);
    	return ;
    }
    int main(){
    	N=read();M=read();
    	for(int i=1;i<=N;i++){
    		++tot;
    		q[tot].op=0;q[tot].x=i;q[tot].y=read();
    	}
    	for(int i=1;i<=M;i++){
    		++tot;
    		q[tot].op=i;q[tot].x=read(),q[tot].y=read(),q[tot].z=read();
    	}
    	solve(-INF,INF,1,tot);
    	for(int i=1;i<=M;i++)printf("%d
    ",ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    2015.05.12:json的常用处理方式
    好菜都得花功夫做
    网站从 IIS6 迁移至 IIS7 后的通配符脚本映射权限配置不当可能导致403错误
    ELearning是学习系统而不是教育系统
    空难与软件开发(一)
    “模态”对话框和“后退”按钮
    闭包解析(Fun with closure)
    关于CultureInfo的两个问题
    从来就不可能精通:关于Boxing
    Windows RT 应用程序开发介绍培训的讲义
  • 原文地址:https://www.cnblogs.com/martian148/p/13900354.html
Copyright © 2020-2023  润新知