BZOJ 3110 K大数查询(线段树套线段树)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=3110

题意:有N个位置,M个操作。操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置插入一个数c;如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少。(题目说对于操作1,abs(c)<=N,其实数据中没有c为负数的)

思路:两层线段树,第一层以插入的数字建树,称这个线段树为A,第二层以N个位置建树,称这种线段树为B。A的每个节点都有一个B节点。比如所有插入数字范围为[1,8],有8个位置,N=8。某一次在区间[1,4]插入数字2,我们从A中看到,包含数字2的节点为[1,8],[1,4],[1,2],[2,2],更新时A的每个节点对应的B都插入一次区间[1,4]。统计区间第K大值时,假设当前为A的某个节点p,我们首先在p的右子树中统计总个数是否大于K(这里其实就是在p的右孩子对应的B节点统计总个数),若大于则在右子树查找,否则在左子树。

 




struct node1
{
    int L,R,t;
};


struct node2
{
    int L,R,det;
    i64 sum;
};


node1 a1[N];
node2 a2[N];
int e1,e2;




int n,m;


void pushDown(int t)
{
    if(!a2[t].det) return;
    if(!a2[t].L) a2[t].L=++e2;
    if(!a2[t].R) a2[t].R=++e2;
    a2[a2[t].L].det+=a2[t].det;
    a2[a2[t].R].det+=a2[t].det;
    a2[t].det=0;
}

void cover(int t,int L,int R,int l,int r)
{
    if(L>R) return;
    if(L==l&&R==r)
    {
        a2[t].det++;
        return;
    }
    pushDown(t);
    int mid=(L+R)>>1;
    if(r<=mid)
    {
        if(!a2[t].L) a2[t].L=++e2;
        cover(a2[t].L,L,mid,l,r);
    }
    else if(l>mid)
    {
        if(!a2[t].R) a2[t].R=++e2;
        cover(a2[t].R,mid+1,R,l,r);
    }
    else
    {
        if(!a2[t].L) a2[t].L=++e2;
        if(!a2[t].R) a2[t].R=++e2;
        cover(a2[t].L,L,mid,l,mid);
        cover(a2[t].R,mid+1,R,mid+1,r);
    }
    a2[t].sum=0;
    int x=a2[t].L,y=a2[t].R;
    if(x) a2[t].sum+=a2[x].det*(mid-L+1)+a2[x].sum;
    if(y) a2[t].sum+=a2[y].det*(R-mid)+a2[y].sum;
}

void build(int t,int L,int R,int pos,int l,int r)
{
    if(L>R) return;
    if(!a1[t].t) a1[t].t=++e2;
    cover(a1[t].t,1,n,l,r);
    if(L==R) return;
    int mid=(L+R)>>1;
    if(pos<=mid)
    {
        if(!a1[t].L) a1[t].L=++e1;
        build(a1[t].L,L,mid,pos,l,r);
    }
    else
    {
        if(!a1[t].R) a1[t].R=++e1;
        build(a1[t].R,mid+1,R,pos,l,r);
    }
}


int find(int t,int L,int R,int l,int r)
{
    if(L>R) return 0;
    if(L==l&&R==r) return a2[t].det*(r-l+1)+a2[t].sum;
    pushDown(t);
    int mid=(L+R)>>1;
    i64 ans=0;
    if(r<=mid) ans=find(a2[t].L,L,mid,l,r);
    else if(l>mid) ans=find(a2[t].R,mid+1,R,l,r);
    else
    {
        if(a2[t].L) ans+=find(a2[t].L,L,mid,l,mid);
        if(a2[t].R) ans+=find(a2[t].R,mid+1,R,mid+1,r);
    }
    a2[t].sum=0;
    int x=a2[t].L,y=a2[t].R;
    if(x) a2[t].sum+=a2[x].det*(mid-L+1)+a2[x].sum;
    if(y) a2[t].sum+=a2[y].det*(R-mid)+a2[y].sum;
    return ans;
}


int query(int t,int L,int R,int K,int l,int r)
{
    if(L>R) return 0;
    if(L==R) return L;
    int mid=(L+R)>>1;
    int temp;
    if(a1[t].R)
    {
        temp=find(a1[a1[t].R].t,1,n,l,r);
        if(temp>=K) return query(a1[t].R,mid+1,R,K,l,r);
        K-=temp;
    }
    return query(a1[t].L,L,mid,K,l,r);
}


int main()
{
    RD(n,m);
    int op,a,b,c;
    while(m--)
    {
        RD(op); RD(a,b,c);
        if(op==1) build(0,0,n,c,a,b);
        else PR(query(0,0,n,c,a,b));
    }
}

你可能感兴趣的:(线段树)