HYSBZ - 3110 K大数查询 (树套树 整体二分)

有N个位置,M个操作。操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c
如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少。

Input

第一行N,M
接下来M行,每行形如1 a b c或2 a b c

Output

输出每个询问的结果

Sample Input

2 5 1 1 2 1 1 1 2 2 2 1 1 2 2 1 1 1 2 1 2 3

Sample Output

1 2 1

Hint



【样例说明】

第一个操作 后位置 1 的数只有 1 , 位置 2 的数也只有 1 。 第二个操作 后位置 1

的数有 1 、 2 ,位置 2 的数也有 1 、 2 。 第三次询问 位置 1 到位置 1 第 2 大的数 是

1 。 第四次询问 位置 1 到位置 1 第 1 大的数是 2 。 第五次询问 位置 1 到位置 2 第 3

大的数是 1 。‍

N,M<=50000,N,M<=50000

a<=b<=N

1操作中abs(c)<=N

2操作中c<=Maxlongint

解题思路: 树套树的解法就是外层对值域建立线段树,内层对区间建立线段树。

那么对于更细操作就是在外层找到这个值,并且把路径上的区间整体加1。

对于一个查询操作就是类似于主席树查询区间第k大的转移操作。在外层二分找到那个值就可以了。

内层要动态开点。

另外,以前线段树查询修改之类的函数总爱写成递归的形式,在学习的时候发现了一个很好的模板

看这里

ps:只有内层才支持区间修改(或者可以用标记永久化。)。

树套树在bzoj上过不去。。在洛谷上吸氧还是有一组T掉了。。。。

#include<iostream>
#include<cstdio>
using namespace std;
#define LL long long
#define N 50005
struct node
{
   int ls,rs,lazy;
   LL sum;
}t[20000000];
int tot;
int T[20000000];
int n;

void push_down(int rt,int len)
{
    if(t[rt].lazy)
    {
        if(!t[rt].ls)t[rt].ls=++tot;
        if(!t[rt].rs)t[rt].rs=++tot;
        int tmp=t[rt].lazy;
        int llen=len-len/2,rlen=len/2;
        t[t[rt].ls].sum+=tmp*llen;
        t[t[rt].rs].sum+=tmp*rlen;
        t[t[rt].ls].lazy+=tmp;
        t[t[rt].rs].lazy+=tmp;
        t[rt].lazy=0;
    }
}

void update(int &rt,int l,int r,int ql,int qr)
{
    if(!rt)rt=++tot;
    if(l>=ql &&r<=qr)
    {
        t[rt].sum+=r-l+1;
        t[rt].lazy+=1;
        return ;
    }
    push_down(rt,r-l+1);
    int m=(l+r)>>1;
    if(ql<=m) update(t[rt].ls,l,m,ql,qr);
    if(qr>m) update(t[rt].rs,m+1,r,ql,qr);
    t[rt].sum=t[t[rt].ls].sum+t[t[rt].rs].sum;
}

void change(int a,int b,int c)
{
    int rt=1,l=1,r=n;
    while(l!=r)
    {
        int m=(l+r)>>1;
        update(T[rt],1,n,a,b);
        if(c<=m)r=m,rt<<=1;
        else l=m+1,rt=rt<<1|1;
    }

    update(T[rt],1,n,a,b);
}

LL query_sub(int rt,int l,int r,int ql,int qr)
{
    if(!rt)return 0;
    if(l>=ql && r<=qr)
    {
        return t[rt].sum;
    }
    push_down(rt,r-l+1);
    LL ans=0;
    int m=(l+r)>>1;
    if(ql<=m) ans+=query_sub(t[rt].ls,l,m,ql,qr);
    if(qr>m) ans+=query_sub(t[rt].rs,m+1,r,ql,qr);
    return ans;
}
int query(int a,int b,LL c)
{
    int rt=1,l=1,r=n;
    while(l!=r)
    {
        int m=(l+r)>>1;
        LL ans=query_sub(T[rt<<1],1,n,a,b);
        if(c<=ans) r=m,rt<<=1;
        else l=m+1,rt=rt<<1|1,c-=ans;
    }
    return l;
}

int main()
{
    int m;
    scanf("%d%d",&n,&m);
    int op,a,b,c;
    while(m--)
    {
        scanf("%d%d%d",&op,&a,&b);
        if(op==1)
        {
            scanf("%d",&c);
            change(a,b,n-c+1);
        }
        else
        {
            LL tmp;
            scanf("%lld",&tmp);
            printf("%d\n",n-query(a,b,tmp)+1);
        }
    }
}

看率到整体二分的作法:

我们将操作和答案一起二分。

如果当前为修改操作,如果加入的数小于当前的mid的值就对于左区间有贡献(并且统计贡献),否则的话就对右区间有贡献(暂且不统计贡献,直接归于右区间)。

如果当前操作为查询操作,查询当前范围内的值,如果大于它的话归于做区间,否则的话就减掉贡献,归于右区间。

#include<iostream>
#include<cstdio>
using namespace std;
#define LL long long
#define N 50005
#define inf 0x3f3f3f3f

struct node
{
    int op,a,b;
    LL c;
    int id;
}p[N],p1[N],p2[N];

int ou[N];

struct tree
{
    LL sum[N*4];
    int lazy[N*4];
    int n;


    void push_down(int rt,int len)
    {
        if(lazy[rt])
        {
            int llen=len-len/2;
            int rlen=len/2;
            sum[rt<<1]+=llen*lazy[rt];
            sum[rt<<1|1]+=rlen*lazy[rt];
            lazy[rt<<1]+=lazy[rt];
            lazy[rt<<1|1]+=lazy[rt];
            lazy[rt]=0;
        }
    }

    void update(int rt,int l,int r,int ql,int qr,int val)
    {
        if(l>=ql && r<=qr)
        {
            sum[rt]+=(r-l+1)*val;
            lazy[rt]+=val;
            return ;
        }
        push_down(rt,r-l+1);
        int m=(l+r)>>1;
        if(ql<=m) update(rt<<1,l,m,ql,qr,val);
        if(qr>m) update(rt<<1|1,m+1,r,ql,qr,val);
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    }

    LL query(int rt,int l,int r,int ql,int qr)
    {
        if(l>=ql && r<=qr)
        {
            return sum[rt];
        }
        push_down(rt,r-l+1);
        LL ans=0;
        int m=(l+r)>>1;
        if(ql<=m) ans+=query(rt<<1,l,m,ql,qr);
        if(qr>m) ans+=query(rt<<1|1,m+1,r,ql,qr);
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
        return ans;
    }

    void add(int l,int r,int v)
    {
        update(1,1,n,l,r,v);
    }

    LL ask(int l,int r)
    {
        return query(1,1,n,l,r);
    }
}T;
void solve(int ql,int qr,int l,int r)
{
    if(ql>qr)return ;
    if(l==r)
    {
        for(int i=ql;i<=qr;i++){
            if(p[i].op==2)ou[p[i].id]=l;
        }
        return ;
    }

    int m=(l+r)>>1;
    int L=0,R=0;
    for(int i=ql;i<=qr;i++)
    {
        if(p[i].op==1)
        {
            if(p[i].c<=m)
            {
                p1[L++]=p[i];
                T.add(p[i].a,p[i].b,1);
            }
            else p2[R++]=p[i];
        }
        else
        {
            LL res=T.ask(p[i].a,p[i].b);
            if(res>=p[i].c) p1[L++]=p[i];
            else
            {
                p[i].c-=res;
                p2[R++]=p[i];
            }
        }
    }

    for(int i=0;i<L;i++){
        if(p1[i].op==1){
            T.add(p1[i].a,p1[i].b,-1);
        }
    }

    int now=ql;
    for(int i=0;i<L;i++)p[now++]=p1[i];
    for(int i=0;i<R;i++)p[now++]=p2[i];
    solve(ql,ql+L-1,l,m);
    solve(ql+L,ql+L+R-1,m+1,r);

}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    T.n=n;
    int op,a,b;
    LL c;
    int id=0;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d%lld",&op,&a,&b,&c);
        if(op==1)c=n-c+1;
        if(op==2)id++;
        p[i]=node{op,a,b,c,id};
    }
    solve(1,m,-n,n);
    for(int i=1;i<=id;i++)printf("%d\n",n+1-ou[i]);
}

另外,如果把整体二分里边的线段树换为树状数组的话应该会更快。

猜你喜欢

转载自blog.csdn.net/weixin_40894017/article/details/88552576