caioj1097: [视频]树状数组1(快速求和计算) cdq分治入门

这题虽然是个树状数组,但是也可以用cdq分治做啊~~,这个就是一个浅显的二维偏序的应用?

cdq分治和普通的分治有什么区别?

举个栗子:有4个小朋友,你请他们吃饭,假如你分治搞,就会分成很多子问题——1~1号小朋友有多少个来,2~2号小朋友有多少个来,然后程序就会回溯,你就知道1~2号小朋友有多少个来,最后你就知道1~4号小朋友有多少个来了。

而cdq分治呢?同样是4个小朋友,但是要照顾小朋友的心情,第i号小朋友的开心程度是1~i-1号小朋友有多少个来,你想知道小朋友们的心情,有可能心情不好就不来了,那先往左右搜,然后要把心情的影响加上。可能这个栗子不是很贴切,但是我想说的就是cdq分治中,前面的结果会影响后面的,而且,这个算法做题只能离线。

那么,我是拿了这题水题来理解。

俩操作,一个单点修改,一个询问区间和。

那么我们还是照样按前缀和的做法,把询问分成求1~l-1和1~r。

那么离线做,按照输入的顺序放进结构体,这个时候你一定发现了,对于一个询问,对它有影响的修改,就是在它前面操作的,并且修改位置也在它询问位置前面的。那么,实际上这个结构体已经是按照操作的顺序排好了,那么要解决的就是位置的问题。具体的做法,就是按照它的位置,做一次归并排序。

具体怎么做呢,首先先把区间分成l~mid和mid+1~r,当前我们要维护的,就是mid+1~r的全部询问,要先让两边都递归下去,令l~mid的询问解决,以及mid+1~r里的修改施加影响。

那么在归并的过程中,记录一个sum,表示左边已经l~当前归并到的位置的修改值的和,然后当遇到右边的询问,就将影响释放下去,一直这样做下去,当按x排好序了,对于mid+1~r中的询问,l~r的全部影响都已经解决,最后回溯接受更前面的影响,或者去影响后面的其他询问(你这样归排岂不是把一开始操作的顺序打乱了!?不用担心,l~mid的输入顺序每一个都是比mid+1~r大的,l~mid的顺序改变并不会影响后面的修改

 

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;

int n,m;
struct node
{
    int x,tp;LL v;
    //tp=1表示这个是修改操作,x表示修改哪个位置,v表示要加上的值
    //tp=2,3表示这个是问1~x的前缀和,v表示这个影响第几个答案 
}a[210000],t[210000];int len=0;
void ins(int x,int tp,int v)
{
    len++;
    a[len].x=x;a[len].tp=tp;a[len].v=v;
}
LL ans[210000];int ansl=0;//答案数组
bool check(node n1,node n2)
{
    if(n1.x<n2.x||(n1.x==n2.x&&n1.tp<n2.tp))return true;
    return false;
}
void cdq(int l,int r)
{
    if(l==r)return ;
    int mid=(l+r)/2;
    cdq(l,mid);cdq(mid+1,r);
    
    LL sum=0;//sum表示l~t[p].x的修改的和
    int i=l,j=mid+1,p=l;
    while(i<=mid&&j<=r)
    {
        if(check(a[i],a[j])==true)//只统计左边区间内的修改值,因为右边的修改值已经在递归里影响过了 
        {
            if(a[i].tp==1)sum+=a[i].v;
            t[p++]=a[i++];
        }
        else//将左边的影响影响右边 
        {
                 if(a[j].tp==2)ans[a[j].v]-=sum;
            else if(a[j].tp==3)ans[a[j].v]+=sum;
            t[p++]=a[j++];
        }
    }
    while(i<=mid)t[p++]=a[i++];
    while(j<=r)
    {
             if(a[j].tp==2)ans[a[j].v]-=sum;
        else if(a[j].tp==3)ans[a[j].v]+=sum;
        t[p++]=a[j++];
    }
    
    for(int i=l;i<=r;i++)a[i]=t[i];
}

char ss[10];
int main()
{
    int x,l,r;LL v;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    { 
        scanf("%s",ss+1);
        if(ss[1]=='C')
        {
            scanf("%d%lld",&x,&v);
            ins(x,1,v);
        }
        else//利用前缀和的思想,把查询操作分为两部分
        {
            scanf("%d%d",&l,&r);
            ansl++;ins(l-1,2,ansl);ins(r,3,ansl);
        }
    }
    cdq(1,len);
    for(int i=1;i<=ansl;i++)printf("%lld\n",ans[i]);
    return 0;
}

转载于:https://www.cnblogs.com/AKCqhzdy/p/8017372.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值