CDQ分治的一些理解

我是看这个学会的mlystdcall
咕咕咕了好久的一个东西。
使用该算法的两个条件:

  1. 题目允许使用离线算法;
  2. 答案无后效性,即修改区间中位置上5的值,不会对区间[1,4]的查询产生影响。

定义 s o l v e ( l , r ) solve(l,r) solve(l,r)表示对于 ∀ k ∈ [ l , r ] {\forall}k\in[l,r] k[l,r]若第 k k k项操作是查询,则计算第 [ l , k − 1 ] [l,k-1] [l,k1]中的所有修改操作对它造成的影响。
计算方法:

  1. m i d = ( l + r ) > > 1 mid=(l+r)>>1 mid=(l+r)>>1,递归计算 s o l v e ( l , m i d ) solve(l,mid) solve(l,mid)
  2. 递归计算 s o l v e ( m i d + 1 , r ) solve(mid+1,r) solve(mid+1,r)
  3. 计算 [ l , m i d ] [l,mid] [l,mid]中所有的修改对 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]中的所有查询造成的影响。

为什么这样做是对的?
对于 [ l , m i d ] [l,mid] [l,mid]中的所有修改对查询造成的影响已经在递归处理 s o l v e ( l , m i d ) solve(l,mid) solve(l,mid)中计算完毕了, [ m i d + 1 , r ] [mid+1,r] [mid+1,r]中的也已经递归计算完毕了,那么只剩下 [ l , m i d ] [l,mid] [l,mid]中的修改对于 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]中的查询造成的影响了,故最后处理这一部分的影响。
从一道例题出发吧:
支持单点修改,区间查询。
很裸的树状数组。
这里拿cdq来写,定义结构体:

struct Node{
    int type;//操作类型;
    int idx;//操作的位置;
    int val;//修改的值或者表示第几个查询;
    bool operator <(const Node& x)const{//优先按照操作位置,其次是修改优于查询;
        if(idx!=x.idx) return idx<x.idx;
        return type<x.type;
    }
};

修改操作type为1,然后将查询 [ l , r ] [l,r] [l,r]操作拆解成两个, s u m [ r ] − s u m [ l − 1 ] sum[r]-sum[l-1] sum[r]sum[l1],对于 l − 1 l-1 l1的查询,type=2, r r r的查询,type=3。
因为我们在输入的时候就是按照操作的时间顺序进行输入的,所以在分治过程中拆成的两半区间,左区间的所有操作一定是发生在右区间之前的,所以我们只记录左边的修改造成的影响,然后将其加到对应的右边的操作即可。
注意到我们查询拆成了两个前缀和的形式,那么判断修改对查询是否造成了影响,只需要

  1. 在时间线上满足时间顺序
  2. 修改位置要小于查询位置,(修改5位置上的数字,只会对查询 [ 5 , + o o ] [5,+oo] [5,+oo]才会产生影响)‘’
    P2068 统计和
    这道题数列没有初始值,如果有初始值的话,可以将初始值看成修改操作即可。
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;


const int maxn=1e5+7;

const int maxm=2e4+7;

struct Node{
    int type;
    int idx;
    int val;
    bool operator <(const Node& x)const{
        if(idx!=x.idx) return idx<x.idx;
        return type<x.type;
    }
}query[maxm],temp[maxm];

ll ans[maxm];

void CDQ(int l,int r){
    if(l==r) return ;
    int mid=(l+r)>>1;
    CDQ(l,mid);
    CDQ(mid+1,r);

    int p=l,q=mid+1;
    ll sum=0;
    int o=0;
    while(p<=mid&&q<=r){
        if(query[p]<query[q]){//只记录左边的修改造成的影响;
            if(query[p].type==1) sum+=query[p].val;
            temp[o++]=query[p++];
        }
        else{//说明在此刻,右边的操作的位置要大于左边操作的位置,
        	//那么由于我们记录的是前缀和,故这一刻,左边的修改就需要加到右边的这次查询中了。
            if(query[q].type==2) ans[query[q].val]-=sum;
            else if(query[q].type==3) ans[query[q].val]+=sum;
            temp[o++]=query[q++];
        }
    }
    while(p<=mid) temp[o++]=query[p++];
    while(q<=r){
        if(query[q].type==2) ans[query[q].val]-=sum;
        else if(query[q].type==3) ans[query[q].val]+=sum;
        temp[o++]=query[q++];
    }

    for(int i=0;i<o;++i) query[l+i]=temp[i];
}
char s[9];
int main(){
    int n,w;
    scanf("%d%d",&n,&w);
    int l,r,id,x;
    int idx=0,time=0;
    for(int i=1;i<=w;++i){
        scanf("%s",s);
        if(s[0]=='x'){
            scanf("%d%d",&l,&x);
            ++idx;
            query[idx].type=1,query[idx].idx=l,query[idx].val=x;
        }
        else{
            scanf("%d%d",&l,&r);
            ++idx;
            ++time;
            query[idx].type=2,query[idx].idx=l-1,query[idx].val=time;
            ++idx;
            query[idx].type=3,query[idx].idx=r,query[idx].val=time;
        }
    }
    CDQ(1,idx);
    for(int i=1;i<=time;++i) printf("%lld\n",ans[i]);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值