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

Posted Candy?

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[用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, r
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 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$乘移动的距离就可以了

其实本质就是得到了计算$[l,mid]$中修改的贡献后每一个位置的值应该是多少

问题解决!撒花

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】应用

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

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

2.可以推广到一系列区间修改与查询问题

 

以上是关于[用CDQ分治解决区间加&区间求和]习作的主要内容,如果未能解决你的问题,请参考以下文章

CDQ分治学习

CDQ分治

cdq分治解决区间问题

初窥CDQ分治

浅谈CDQ分治

luogu U33697 寻找宝藏 (cdq分治+dp)