• [学习笔记] CDQ分治


    听娜姐讲完FFT,一脸懵逼,还是来讲讲【CDQ分治】吧。。。

    用途:

    解决 “带时间轴的更改和查询” 问题。

    算法流程:

    首先我们要知道,这个算法是离线的,还是利用递归进行求解的。

    1. 把读入的n个操作都按照时间轴排列好。
    2. 把时间轴劈开,分为[l,mid]和[mid+1,r]两部分。
    3. 开始当前层cdq(l,r)的求解前先进行cdq(l,mid)与cdq(mid+1,r)的求解。
    4. 拎出区间[l,mid]内的所有修改操作和区间[mid+1,r]内的所有查询操作[mid+1,r]。
    5. 统计区间[l,mid]内的所有修改操作对区间[mid+1,r]内的所有查询操作的影响,并将影响加入ans数组中。

    关于算法的正确性:

    为什么这么做会是对的呢?我们不妨举个例子来看看。

    加入一共有7个时刻(或者说是个7操作),对于位于3时刻的修改操作,在cdq(1,7)中,统计了它对于区间[5,7]中查询的影响,又在cdq(3,4)中统计了区间[4,4]中查询的影响。

    可以看到,对于位于3时刻的查询操作,在我们的cdq过程中,统计了它对于[4,7]的时刻中,所有查询操作的影响。

    例题以及题解:

    例题:

    BOI 2007 【Mokia 摩基亚】     洛谷里有,P4390。

    题目概述:

    (至于为什么是概述呢?——题目B话太TM多了!!!

    给你一个二维平面直角坐标系,还有一些操作。

    操作有两种:

    1.格式"(1,x,y,w)",表示在(x,y)的地方加上权值w。
    2.格式"(2,sx,sy,ex,ey)",表示询问以(sx,sy)为左上角顶点,(ex,ey)为右下角顶点,询问矩形区间内的权值和。

    后面的修改操作不会对前面的查询操作产生影响。

    数据:操作数<=200000,x,y,sx,sy,ex,ey<=2000000

    思路分析:

    大家还是先打消要用二维树状数组的念头吧

    是二维的,那么我们应该怎么做呢?(先提示下吧,log2的,大家可以先想一想)

    滴,滴,滴,滴————————

    好,时间到,那就再给大家一个提示吧。

    某名Niro**的dalao(他/她 真的是dalao)说过:“CDQ分治就是花log n的时间,把所有的修改操作都移到查询操作之前。”

    再想想。。。。。。

    (假如说还没有想到的话,可以先看一下下文中那些大号加粗的字,看看有没有思路啥的)

    (想到了的话,就可以把这篇博客关掉了,再顺手点个推荐啥的

    嗯,现在就到我讲了吧!

    二维的,又不能用二维树状数组,再看看博客标题——“CDQ分治”。

    en~~~~~

    那就肯定是CDQ分治啦!!!

    那么当我们把所有的修改都放在查询后面之后,我们应该怎么做呢?

    显然要对问题进行“降维打击”

    这个降维打击是个啥呢?——就是把1、2操作(其中把2操作变成起始和结束的两个点)对于x轴进行排序嘛。

    排序之后,我们就可以保证在x轴上,前面做过的修改操作,在x轴上是永远包含于后面的查询操作的(如果它的x坐标大于当前查询操作右端点对应的左端点的)。

    那么我们只要维护y轴就可以了,这个可以用树状数组来维护。

    具体方法的话就是,给树状数组中修改点的y坐标加上它的权值,然后遇到查询操作的右端点时,就统计一下y取值范围内的和就可以了。

    那么最后的一个问题就是如果修改点的x坐标小于查询当前查询操作右端点对应的左端点我们应该怎么办呢?

    大家先思考一下吧。

    ......

    ~~~好了吗?我说喽。——差分嘛!

    具体方法就是——遇到左端点时,先把当前树状数组中属于询问范围的y的区间和给减掉嘛。

    代码实现:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=200005;
    struct point{
        int x,y,id;
    };
    point a[N],b[N<<1];
    int x[N],y[N],sx[N],sy[N],ex[N],ey[N],w[N],ans[N],c[2000005],acnt,bcnt,tot,opt;
    bool cmp (point a,point b){
        return a.x<b.x;
    }
    void update(int x,int val){
        if (x<=2000000) c[x]+=val,update(x+(x&(-x)),val);
    }
    int query(int x){
        if (x>0) return c[x]+query(x-(x&(-x))); else return 0;
    }
    void cdq(int l,int r){
        if (l==r) return;
        int mid=l+r>>1; 
        cdq(l,mid); cdq(mid+1,r);
        acnt=0,bcnt=0;
        for (int i=l;i<=mid;i++)
            if (x[i]>0) ++acnt,a[acnt].x=x[i],a[acnt].y=y[i],a[acnt].id=i;
        for (int i=mid+1;i<=r;i++)
            if (sx[i]>0) 
                ++bcnt,b[bcnt].x=sx[i]-1,b[bcnt].y=sy[i],b[bcnt].id=i,
                ++bcnt,b[bcnt].x=ex[i],b[bcnt].y=ey[i],b[bcnt].id=i;
        sort(a+1,a+1+acnt,cmp);
        sort(b+1,b+1+bcnt,cmp);
        int i=1,j=1;
        while (i<=acnt||j<=bcnt)
            if (j>bcnt||i<=acnt&&a[i].x<=b[j].x) 
                update(a[i].y,w[a[i].id]),++i;
            else 
                if (b[j].x==sx[b[j].id]-1&&b[j].y==sy[b[j].id]) 
                    ans[b[j].id]-=query(ey[b[j].id])-query(b[j].y-1),j++;
                else 
                    ans[b[j].id]+=query(b[j].y)-query(sy[b[j].id]-1),j++;
        for (int i=l;i<=mid;i++) 
            if (x[i]>0) update(y[i],-w[i]);
    }
    int main(){
        scanf("0 %d",&w[0]);
        scanf("%d",&opt);
        while (opt!=3){
            if (opt==1) ++tot,scanf("%d%d%d",&x[tot],&y[tot],&w[tot]);
            else ++tot,scanf("%d%d%d%d",&sx[tot],&sy[tot],&ex[tot],&ey[tot]);
            scanf("%d",&opt);
        }
        cdq(1,tot);
        for (int i=1;i<=tot;i++)
            if (sx[i]>0) printf("%d
    ",ans[i]);
        return 0;
    }
  • 相关阅读:
    C++ for(char c:s)遍历字符串||for (char c : s)和for (char& c : s)的区别
    二维数组的查找--剑指offer(C++)
    C++学习笔记之--boolalpha
    在C++中matrix.size()和matrix [0] .size()之间的区别是什么?
    C3_note
    用webpack4从零开始构建react脚手架
    php
    正则表达式基础
    DOM
    常用H5
  • 原文地址:https://www.cnblogs.com/WR-Eternity/p/10079431.html
Copyright © 2020-2023  润新知