BZOJ[1176]Mokia CDQ分治

10 篇文章 0 订阅
2 篇文章 0 订阅

传送门ber~

个人理解CDQ就是降维用的…
就比如这道题,一个询问可以拆成四个
把所有的问题分治,用归并排序的思想将 l l ~mid mid+1 m i d + 1 ~ r r 都按x这一维排好序,就可以算作把 x x 这一维降掉
考虑左区间对右区间的影响,对于右区间的某次询问x1,y1左区间的所有 xx1 x ≤ x 1 的修改都可能对其产生影响
换句话说,对于单次询问 x1,y1 x 1 , y 1 ,事实上只考虑在他之前的,所有 xx1 x ≤ x 1 的询问就可以了
那么枚举右区间的查询操作 i i ,并按x从小到大的顺序向树状数组中添加左区间的修改操作,同时满足树状数组里所有修改操作的 x x 均小于等于这个询问的xi,那么这个询问的解就加上树状数组的 Query(y) Q u e r y ( y ) ,这个 Query(y) Q u e r y ( y ) 意义就是本层左区间的影响,又因为左右区间都已经用归并的方式将其按照 x x <script id="MathJax-Element-17" type="math/tex">x</script>排了序,所以搞个双指针扫一遍就可以了

说的不是很清楚,还是看代码理解吧…

代码如下:

#include<algorithm>
#include<ctype.h>
#include<cstdio>
#define INF 2147483647
#define N 2000050
using namespace std;
inline int read(){
    int x=0,f=1;char c;
    do c=getchar(),f=c=='-'?-1:f; while(!isdigit(c));
    do x=(x<<3)+(x<<1)+c-'0',c=getchar(); while(isdigit(c));
    return x*f;
}
int n,s,x,y,x1,y1,k,opt,top,Timer;
int a[N],time[N];
struct Data{
    int x,y,k,num,ans;
    Data(int _x=0,int _y=0,int _k=0,int _num=0,int _ans=0):x(_x),y(_y),k(_k),num(_num),ans(_ans){}
}q[N],b[N];
inline bool cmp(Data a,Data b){
    return a.num<b.num;
}
inline void Add(int x,int v){///树状数组修改
    for(;x<=n;x=x+(x&-x)){
        if(time[x]!=Timer){
            time[x]=Timer;
            a[x]=0;
        }
        a[x]=a[x]+v;
    }
}
inline int Query(int x){///树状数组查询
    int tmp=0;
    for(;x;x=x-(x&-x))
        if(time[x]==Timer)
            tmp=tmp+a[x];
    return tmp;
}
void CDQ(int l,int r){
    if(l==r) return;
    int mid=(l+r)>>1;
    CDQ(l,mid);CDQ(mid+1,r);///分治左右区间
    Timer++;///清空树状数组,memset会超时故使用时间戳
    int j=l,i=mid+1;///i扫右区间,j扫左区间
    for(;i<=r;i++){
        while(q[j].x<=q[i].x && j<=mid){///将左区间x小于当前询问x的修改都落实
            if(q[j].k!=INF) Add(q[j].y,q[j].k);///q[j].k==INF代表是查询操作
            j++;
        }
        if(q[i].k==INF) q[i].ans+=Query(q[i].y);///加上本层左区间的影响
    }
    i=l;j=mid+1;///下面全是归并排序
    int top=l;
    while(i<=mid && j<=r){
        if(q[i].x<q[j].x) b[top++]=q[i++];
        else b[top++]=q[j++];
    }
    while(i<=mid) b[top++]=q[i++];
    while(j<=r) b[top++]=q[j++];
    for(int i=l;i<=r;i++)
        q[i]=b[i];
    return;
}
int main(){
    s=read();n=read();
    while(""){
        opt=read();
        if(opt==3) break;
        if(opt==1){
            x=read();y=read();k=read();
            top++;q[top]=Data(x,y,k,top,0);
        }
        else{
            x=read();y=read();x1=read();y1=read();
            top++;q[top]=Data(x1,y1,INF,top,x1*y1*s);///拆询问
            top++;q[top]=Data(x-1,y-1,INF,top,(x-1)*(y-1)*s);
            top++;q[top]=Data(x-1,y1,INF,top,(x-1)*y1*s);
            top++;q[top]=Data(x1,y-1,INF,top,x1*(y-1)*s);
        }
    }
    CDQ(1,top);
    sort(q+1,q+top+1,cmp);
    for(int i=1;i<=top;i++)
        if(q[i].k==INF){
            printf("%d\n",q[i].ans+q[i+1].ans-q[i+2].ans-q[i+3].ans);///统计答案
            i=i+3;
        }
return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值