[BZOJ3110][ZJOI2013]K大数查询 树套树/CDQ分治

树套树做法:
注意到权值很小,于是外层开权值线段树,内层是一个动态开点的区间线段树,维护权值在[L,R],位置在[l,r]的数一共有多少个。
修改就是内层的一个线段树上区间加一。
查询时,外层线段树中先判断左子树中够不够k个数,若够则往左子树递归,不够就减一下后往右子树。
代码(MLE):

#include<iostream>
#include<cstdio>
using namespace std;
int n,m;
struct tree1
{
    int sum,lz;
    tree1 *ls,*rs;
    tree1()
    {
        sum=lz=0;
        ls=rs=NULL;
    }
    void cal(int x,int l,int r){sum+=x*(r-l+1);}
    void pushdown(int l,int r)
    {
        int mid=(l+r)>>1;
        if(ls==NULL) ls=new tree1;
        if(rs==NULL) rs=new tree1;
        ls->cal(lz,l,mid);rs->cal(lz,mid+1,r);
        ls->lz+=lz;rs->lz+=lz;
        lz=0;
    }
    void update()
    {
        sum=ls->sum+rs->sum;
    }
    void add(int lx,int rx,int l,int r)
    {
        if(lx==l&&rx==r) {lz++;sum+=r-l+1;return;}
        int mid=(l+r)>>1;
        pushdown(l,r);
        if(rx<=mid) ls->add(lx,rx,l,mid);
        else if(lx>mid) rs->add(lx,rx,mid+1,r);
        else ls->add(lx,mid,l,mid),rs->add(mid+1,rx,mid+1,r);
        update();
    }
    int qry(int lx,int rx,int l,int r)
    {
        if(lx==l&&rx==r) return sum;
        pushdown(l,r);
        int mid=(l+r)>>1;
        if(rx<=mid) return ls->qry(lx,rx,l,mid);
        else if(lx>mid) return rs->qry(lx,rx,mid+1,r);
        else return ls->qry(lx,mid,l,mid)+rs->qry(mid+1,rx,mid+1,r);
    }
};
struct tree2
{
    int l,r;
    tree2 *ls,*rs;
    tree1 *rt;
    tree2()
    {
        l=r=0;
        ls=rs=NULL;rt=NULL;
    }
    void build(int lx,int rx)
    {
        l=lx;r=rx;
        //(rt=new tree1)->init(1,n);
        rt=new tree1;
        //cout<<lx<<' '<<rx<<endl;
        if(l==r) return;
        int mid=(l+r)>>1;
        (ls=new tree2)->build(lx,mid);
        (rs=new tree2)->build(mid+1,rx);
    }
    void mdf(int lc,int rc,int c)
    {
        rt->add(lc,rc,1,n);
        if(l==r) return;
        int mid=(l+r)>>1;
        if(c<=mid) ls->mdf(lc,rc,c);
        else rs->mdf(lc,rc,c); 
    }
    int query(int lc,int rc,int k)
    {
        if(l==r) return l;
        int tmp=rs->rt->qry(lc,rc,1,n); 
        if(tmp<k) return ls->query(lc,rc,k-tmp);
        else return rs->query(lc,rc,k);
    }
}*xtr;
int main()
{
    scanf("%d%d",&n,&m);
    (xtr=new tree2)->build(0,n);
    while(m--)
    {
        int opt,a,b,c;
        scanf("%d%d%d%d",&opt,&a,&b,&c);
        if(opt==1) xtr->mdf(a,b,c);
        else printf("%d\n",xtr->query(a,b,c));
    }
    return 0;
}

CDQ分治做法:
solve(S,l,r)表示操作集合是S,且其中所有修改操作的权值都在[l,r]内,询问的答案也在[l,r]内,且所有操作是按时间顺序。
令mid=(l+r)/2,我们可以先做[l,mid]中的修改操作,就可以判断询问答案是在[l,mid]还是在[mid+1,r],然后递归即可。
用树状数组的奇技淫巧维护这个区间加和区间查询,最后记得减回去清空。
代码(AC):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm> 
#define ll long long
using namespace std;
const int maxn=50010;
int n,m,ans[maxn];
ll c[2][maxn];
bool b[maxn];
struct node
{
    int o,x,y,z,id;
}q[maxn],nq[maxn];
bool cmp(node a,node b)
{
    return a.id<b.id;
}
void add(int k,int x,int d)
{
    for(;x<=n;x+=(x&-x)) c[k][x]+=d;
}
ll qry(int k,int x)
{
    ll r=0;
    for(;x;x-=(x&-x)) r+=c[k][x];
    return r;
}
void mdf(int l,int r,int f)
{
    r++; 
    add(0,l,f);add(0,r,-f);add(1,l,f*l);add(1,r,-f*r);
}
ll qsum(int l,int r)
{
    l--;
    return qry(0,r)*(r+1)-qry(1,r)-qry(0,l)*(l+1)+qry(1,l);
}
void solve(int l,int r,int lx,int rx)
{
    if(lx>rx) return ;
    if(l==r)
    {
        for(int i=lx;i<=rx;i++)
            if(q[i].o) ans[q[i].id]=l;
        return ;    
    }
    int mid=(l+r)>>1;
    for(int i=lx;i<=rx;i++) b[i]=0;
    for(int i=lx;i<=rx;i++)
        if(q[i].o)
        {
            int tmp=qsum(q[i].x,q[i].y);
            if(tmp<q[i].z) {b[i]=1;q[i].z-=tmp;}
        }
        else if(q[i].z<=mid) mdf(q[i].x,q[i].y,1); else b[i]=1;
    for(int i=lx;i<=rx;i++)
        if(!q[i].o&&q[i].z<=mid) mdf(q[i].x,q[i].y,-1);
    int top=lx-1,smid;
    for(int i=lx;i<=rx;i++)
        if(!b[i]) nq[++top]=q[i];
    smid=top;   
    for(int i=lx;i<=rx;i++)
        if(b[i]) nq[++top]=q[i];
    for(int i=lx;i<=rx;i++) 
        q[i]=nq[i];
    solve(l,mid,lx,smid);
    solve(mid+1,r,smid+1,rx);               
}
int main()
{
    scanf("%d%d",&n,&m);
    int cnt=0;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d%d",&q[i].o,&q[i].x,&q[i].y,&q[i].z);
        q[i].o--;q[i].id=i;
        if(q[i].o) q[i].id=++cnt;
        else q[i].z=n-q[i].z+1;
    }
    solve(1,n,1,m);
    for(int i=1;i<=cnt;i++)
        printf("%d\n",n-ans[i]+1);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值