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

来源:互联网 发布:cf手游刷等级软件 编辑:程序博客网 时间:2024/05/21 10:52

树套树做法:
注意到权值很小,于是外层开权值线段树,内层是一个动态开点的区间线段树,维护权值在[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 longusing 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;}
原创粉丝点击