首页 > 代码库 > [用CDQ分治解决区间加&区间求和]【习作】

[用CDQ分治解决区间加&区间求和]【习作】

【前言】

作为一个什么数据结构都不会只会CDQ分治和分块的蒟蒻,面对区间加&区间求和这么难的问题,怎么可能会写线段树呢

于是,用CDQ分治解决区间加&区间求和这篇习作应运而生


 

 

【Part.I】区间加&区间求和的数据结构做法

【一】线段树

裸题...

1141ms

 

技术分享
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <cmath>using namespace std;#define lc x<<1#define rc x<<1|1#define mid ((l+r)>>1)#define lson lc, l, mid#define rson rc, mid+1, rtypedef long long ll;const int N=1e5+5;inline ll read(){    char c=getchar();ll x=0,f=1;    while(c<0||c>9){if(c==-)f=-1;c=getchar();}    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}    return x*f;}int n,Q,op,x,y;struct SegmentTree{    struct meow{ ll sum, tag; } t[N<<2];    inline void paint(int x,int l,int r,ll v){        t[x].tag+= v;        t[x].sum+= (r-l+1)*v;    }    inline void pushDown(int x,int l,int r){        if(t[x].tag){            paint(lson, t[x].tag);            paint(rson, t[x].tag);            t[x].tag=0;        }    }    void build(int x,int l,int r){        if(l==r) t[x].sum=read();        else{            build(lson);            build(rson);            t[x].sum=t[lc].sum+t[rc].sum;        }    }    void Add(int x,int l,int r,int ql,int qr,ll v){        if(ql<=l && r<=qr) paint(x,l,r,v);        else{            pushDown(x,l,r);            if(ql<=mid) Add(lson, ql, qr, v);            if(mid<qr)  Add(rson, ql, qr, v);            t[x].sum=t[lc].sum+t[rc].sum;        }    }    ll Que(int x,int l,int r,int ql,int qr){        if(ql<=l && r<=qr) return t[x].sum;        else{            pushDown(x,l,r);            ll ans=0;            if(ql<=mid) ans+=Que(lson, ql, qr);            if(mid<qr)  ans+=Que(rson, ql, qr);            return ans;        }    }}S;int main(){    //freopen("in","r",stdin);    n=read(); Q=read();    S.build(1,1,n);    while(Q--){        op=read();x=read();y=read();        if(op==1) S.Add(1,1,n,x,y,read() );        else printf("%lld\n", S.Que(1,1,n,x,y) );    }}
SegmentTree

 

 

【二】树状数组

考虑每个位置的贡献,维护$a[i]$和$i*a[i]$

477ms

技术分享
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <cmath>using namespace std;typedef long long ll;const int N=1e5+5;inline ll read(){    char c=getchar();ll x=0,f=1;    while(c<0||c>9){if(c==-)f=-1;c=getchar();}    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}    return x*f;}int n,Q,op,x,y;struct meow{    ll c[N];    inline void add(int p,ll v) {for(;p<=n;p+=p&-p) c[p]+=v;}    inline ll sum(int p) {ll re=0; for(;p;p-=p&-p) re+=c[p]; return re;}    inline ll sum(int l,int r) {return sum(r)-sum(l-1);}}C1,C2;struct BinaryIndexTree{    inline void Add(int l,int r,ll v){        C1.add(l,v);   C1.add(r+1,-v);        C2.add(l,l*v); C2.add(r+1,-(r+1)*v);    }    inline ll Que(int l,int r){        return (r-l+1)*C1.sum(1,l) + (r+1)*C1.sum(l+1,r) - C2.sum(l+1,r);    }}A;int main(){//    freopen("in","r",stdin);    n=read(); Q=read();    for(int i=1;i<=n;i++) A.Add(i,i,read() );    while(Q--){        op=read();x=read();y=read();        if(op==1) A.Add(x,y,read() );        else printf("%lld\n", A.Que(x,y) );    }}
BinaryIndexTree

 


 

 

【Part.II】区间加&区间求和的CDQ分治做法

首先我们明确CDQ分治是什么 参见[偏序关系与CDQ分治]【学习笔记】

用CDQ分治解决单点加&区间求和  区间加&单点求值 是很容易的,但要两个都是区间就不太方便了

但经过两个多小时的研究,终于做出来啦

区间加&区间求和可以算是二维偏序问题,可以只用排序和CDQ分治不借助任何数据结构解决

首先把修改和询问都拆成两个

我们对时间排序,对位置进行CDQ分治

问题在于对于$[l,r]$这样一个加操作,$r<p$的当然很方便了,但对$l \le p \le r$的每个$p$贡献都是不一样的

一开始困扰了我好久

突然想到可以维护一个$val$表示当前每在位置上移动一个距离总体的贡献应该改变多少,再维护一个$last$表示上一个位置

然后更新当前的贡献时用$val$乘移动的距离就可以了

问题解决!撒花

1251ms 时间垫底了.....

 

技术分享
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <cmath>using namespace std;typedef long long ll;const int N=1e5+5;inline ll read(){    char c=getchar();ll x=0,f=1;    while(c<0||c>9){if(c==-)f=-1;c=getchar();}    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}    return x*f;}int n,Q,m,op,l,r; ll a[N],v,ans[N];struct meow{    int x,type,qid; ll v;     meow(){}    meow(int b,int c,int d,ll e):x(b),type(c),qid(d),v(e){}    bool operator <(const meow &r) const {return x==r.x ? type<r.type : x<r.x;}}q[N<<2],t[N<<2];void CDQ(int l,int r){     if(l==r) return ;    int mid=(l+r)>>1;    CDQ(l, mid); CDQ(mid+1, r);    int i=l, j=mid+1, p=l;    ll now=0, val=0; int last=0;    while(i<=mid || j<=r){        if(j>r || (i<=mid && q[i]<q[j]) ){            now+=(q[i].x-last)*val; last=q[i].x;            if(q[i].type!=0) val+=q[i].v;            t[p++]=q[i++];        }else{            now+=(q[j].x-last)*val; last=q[j].x;            if(!q[j].type) ans[ q[j].qid ]+= now*q[j].v ;             t[p++]=q[j++];        }    }    for(int i=l;i<=r;i++) q[i]=t[i];}int main(){    freopen("in","r",stdin);    n=read(); Q=read(); int p=0;    for(int i=1;i<=n;i++) v=read(), q[++m]=meow(i-1,-1,0,v), q[++m]=meow(i,1,0,-v);    for(int i=1;i<=Q;i++){        op=read(); l=read(); r=read();         if(op==1) v=read(), q[++m]=meow(l-1,-1,0,v), q[++m]=meow(r,1,0,-v);        else p++, q[++m]=meow(l-1,0,p,-1), q[++m]=meow(r,0,p,1);     }    CDQ(1,m);    for(int i=1;i<=p;i++) printf("%lld\n",ans[i]);}
CDQ分治

 

 


 

【Part.III】应用

这玩意常数又大又需要离线有什么用啊

我们可以出题坐标特别大强制线段树来离线离散化

 

[用CDQ分治解决区间加&区间求和]【习作】