cdq分治入门and持续学习orz

感觉cdq分治是一个很有趣的算法 能将很多需要套数据结构的题通过离线来做

目前的一些微小的理解

在一般情况下 就像求三维偏序xyz

就可以先对x排序 然后分治

1 cdq_x(L,M) ;

2 提取出(L,M)中的修改元素 作为修改操作 提取出(M+1,R)中的查询元素 作为查询操作 然后存入数组q 对q按照y排序

  这样 在q中 关于y 所有的修改操作 都会在 可能作用到的查询操作前面

  似乎可以将这一步 称作"对x维度的剥离" 在对q数组接下来的操作中 不需要考虑x维度 

3 cdq_x(M+1,R);

/// 迷之分析

三维偏序 有时间和xyz
一开始 分两半时间 左边时间一定比右边小 然后记录下左边的修改操作与右边的非修改操作
下一步剥离 对新的区间进行sort 保证了这些修改 查询都各自有意义
sort的是x 则下面的操作中
* 所有的操作 对于其后的询问 都有意义
再次剥离出y 因为之前对x进行了sort
所以之后的新区间(只有z存在大小关系的区间)
前面的半个区间(x层面上的) 对于后半个区间 都有明确的先后关系
而降到z一维的时候 直接树状数组就可以

///迷之证明

如何证明 这些修改和查询都有类似时间线的压制力
一开始的时间可以使first唯独 不可逆
之后的查询都有x1<x2 y1<y2 z1<z2
故若x1>x2则没有意义
1 先剥离出时间维度上 * 左边对且仅对右边有value的操作 即 左改右查 这时是剥离时间
2 剥离x 这时的时间已完全剥离 因为时间影响在"左改右查"中已经去掉 所以剥离x也要"左改右查"
3 到只有 yz 因为y已经被sort 所以直接对z使用树状数组

一般需要一个离散化来做树状数组的相关处理

注意下cdq的各个部分该放在哪里

hdu 5324

求最小字典序的最长偏序序列

cdq分治 由于要记录最小字典序 所以倒着做一下 去掉时间的影响 sort y后 就可以直接x 从R->L 直接树状数组维护了

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<map>
#include<iostream>
#include<string>
#include<queue>
#include<vector>
using namespace std;
#define pb push_back

struct node {
    int x , y  ;
    int id ;
    int xg ;
}a[50050];

int len[50050] ;
int nex[50050] ;

int c[50050 + 500] ;
int tid[50050 + 500] ;

int n ;

node q1[50050] ;

bool cmpy(node a,node b) {
    if(a.y != b.y) return a.y < b.y ;
    return a.x > b.x ;
}

int lowbit(int x) {return (x & (-x)) ; } ;

void cdq_x(int L , int R) {
    for(int i = R ; i >= L ; i -- ) {
        if(q1[i].xg == 1) {
            int lenn = len[q1[i].id] ;
            int nexx = nex[q1[i].id] ;
            for(int j = q1[i].x ; j <= 50050 ; j += lowbit(j)) {
                if(c[j] < lenn) {
                    c[j] = lenn ;
                    tid[j] = q1[i].id ;
                }
                else if(c[j] == lenn) {
                    if(tid[j] > q1[i].id) {
                        tid[j] = q1[i].id ;
                    }
                }
            }
        }
        else {
            int lenn = len[q1[i].id] ;
            int nexx = nex[q1[i].id] ;
            for(int j = q1[i].x ; j > 0 ; j -= lowbit(j)) {
                if(c[j] == 0) continue ;
                if(c[j] + 1 > lenn) {
                    len[q1[i].id] = c[j]+1 ; lenn = len[q1[i].id] ;
                    nex[q1[i].id] = tid[j] ; nexx = nex[q1[i].id] ;
                }
                else if(c[j] + 1 == lenn) {
                    if(tid[j] < nexx) {
                        len[q1[i].id] = c[j]+1 ; lenn = len[q1[i].id] ;
                        nex[q1[i].id] = tid[j] ; nexx = nex[q1[i].id] ;
                    }
                }
            }
        }
    }
    for(int i = L ; i <= R ; i ++ ) {
        if(q1[i].xg == 1) {
            for(int j = q1[i].x ; j <= 50050 ; j += lowbit(j)) {
                tid[j] = 0 ; c[j] = 0 ;
            }
        }
    }
}

void cdq_time(int L , int R) {
    if(L >= R) return ;
    int M = (L + R) / 2 ;
    cdq_time(M+1 , R) ;
    int num = L ;
    for(int i = M+1 ; i <= R ; i ++ ) {
        q1[num] = a[i] ;
        q1[num++].xg = 1 ;
    }
    for(int i = L ; i <= M ; i ++ ) {
        q1[num] = a[i] ;
        q1[num++].xg = 0 ;
    }
    sort(q1+L , q1+num , cmpy) ;
    cdq_x(L , num-1) ;
    cdq_time(L , M) ;
}

int main () {
    while(scanf("%d",&n) != EOF) {
        vector<int >ls ;
        ls.clear() ;
        for(int i=1;i<=n;i++)scanf("%d",&a[i].x) ; /// down
        for(int i=1;i<=n;i++)scanf("%d",&a[i].y) ;
        for(int i=1;i<=n;i++){
            nex[i] = i ;
            len[i] = 1 ;
            a[i].id = i ;
            ls.pb(a[i].x) ;
        }
        sort(ls.begin() , ls.end()) ;
        ls.erase(unique(ls.begin() , ls.end()) , ls.end()) ;
        for(int i = 1 ; i <= n ; i ++ ) {
            int where = lower_bound(ls.begin() , ls.end() , a[i].x) - ls.begin() + 3 ;
            a[i].x = where ;
        }
        cdq_time(1 , n) ;
        int ans = 0 ;
        int le = 0 ;
        for(int i = 1 ; i <= n ; i ++ ) {
            if(len[i] > le) {
                le = len[i] ;
                ans = i ;
            }
        }
        printf("%d
",le) ;
        printf("%d",ans) ;
        while(true) {
            if(nex[ans] == ans) break ;
            ans = nex[ans] ;
            printf(" %d",ans) ;
        }
        printf("
") ;
    }
}

hdu 4742

记录最长三维偏序的方案数

和上一个题类似 但是求得是方案 于是把与c数组并列的tid数组改为次数数组 这个正着做就可以了 

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<map>
#include<vector>
using namespace std;

const int mod = (1<<30) ;

struct node {
    int x , y , z;
    int ansid ;
    int xg ;
}a[100050];
int ans[100050] ;
int len[100050] ;
int n ;

int c[100050+500] ;
int cishu[100050+500] ;

int maxx ;

node q1[100050] , q2[100050] ;

bool cmpx(node a,node b) {
    if(a.x == b.x) {
        if(a.y == b.y) return a.z < b.z ;
        else return a.y < b.y ;
    }
    else return a.x < b.x ;
}
bool cmpy(node a,node b) {
    if(a.y == b.y) {
        return a.z < b.z ;
    }
    else return a.y < b.y ;
}

int lowbit(int x) {return (x & (-x)) ; } ;

void cdq_y(int L , int R) {
    ///printf("-
") ;
    for(int i = L ; i <= R ; i ++ ) {
        if(q2[i].xg == 1) {
            for(int j = q2[i].z ; j <= maxx ; j += lowbit(j) ) {
                if(c[j] < len[q2[i].ansid]) {
                    c[j] = len[q2[i].ansid ] ;
                    cishu[j] = ans[q2[i].ansid] ;
                }
                else if(c[j] == len[q2[i].ansid]) {
                    cishu[j] += ans[q2[i].ansid] ;
                    cishu[j] %= mod ;
                }

            }
        }
        else {
            for(int j = q2[i].z ; j > 0 ; j -= lowbit(j)) {
                if(c[j]+1 > len[q2[i].ansid]) {
                    len[q2[i].ansid] = c[j]+1 ;
                    ans[q2[i].ansid] = cishu[j] ;
                }
                else if(c[j] + 1 == len[q2[i].ansid]) {
                    ans[q2[i].ansid] += cishu[j] ;
                    ans[q2[i].ansid] %= mod ;
                }
            }
        }
    }
    for(int i = L ; i <= R ; i ++ ) {
        if(q2[i].xg == 1) {
            for(int j = q2[i].z ; j <= maxx ; j += lowbit(j)) {
                //if(c[j] == 0) continue ;
                c[j] = 0 ;
                cishu[j] = 0 ;
            }
        }
    }
}

void cdq_x(int L , int R) {
    if(L >= R) return ;
    int M = (L + R) / 2 ;
    cdq_x(L , M) ;
    int num = L ;
    for(int i = L ; i <= M ; i ++ ) {
        q2[num ++ ] = a[i] ;
        q2[num-1].xg = 1 ;
    }
    for(int i = M + 1 ; i <= R ; i ++ ) {
        q2[num ++ ] = a[i] ;
        q2[num-1].xg = 0 ;
    }
    sort(q2+L , q2+num , cmpy) ;
    cdq_y(L , num-1) ;
    cdq_x(M + 1 , R) ;
}

/*void cdq_time(int L , int R) {
    if(L >= R) return ;
    int M = (L + R) / 2 ;
    cdq_time(L , M) ;
    sort(a+L , a+R+1 , cmpx) ;
    cdq_x(L,R) ;
    cdq_time(M+1 , R) ;
}*/

vector<int >ls ;

int main () {
    int T ;
    scanf("%d",&T) ;
    while(T--) {
        scanf("%d",&n) ;
        ls.clear() ;
        memset(c,0,sizeof(c)) ;
        memset(cishu,0,sizeof(cishu)) ;
        for(int i = 1 ; i <= n ; i ++ ) {
            ans[i] = 1 ;
            len[i] = 1 ;
            scanf("%d%d%d",&a[i].x , &a[i].y , &a[i].z) ;
            a[i].ansid = i ;
            ls.push_back(a[i].z) ;
        }
        sort(ls.begin() , ls.end()) ;
        ls.erase(unique(ls.begin() , ls.end()) , ls.end()) ;
        maxx = ls.size() + 20 ;
        for(int i = 1 ; i <= n ; i ++ ) {
            int where = lower_bound(ls.begin() , ls.end() , a[i].z) - ls.begin() + 5 ;
            a[i].z = where ;
        }
        sort(a+1,a+1+n,cmpx) ;
        cdq_x(1 , n) ;
        int realans = 0 ;
        int realcishu = 0 ;
        for(int i = 1 ; i <= n ; i ++ ) {
            if(len[i] > realans) {
                realcishu = ans[i] ;
                realans = len[i] ;
            }
            else if(len[i] == realans) {
                realcishu += ans[i] ;
                realcishu %= mod ;
            }
        }
        printf("%d %d
",realans , realcishu) ;
    }
}

hdu 5126 & bzoj 1935

动态的三维和二维空间询问有多少点

其实是可以直接kd上的...但是也可以cdq 并且省去了写数据结构的麻烦

其实就是把 一个空间的询问 拆分成多个空间->原点的询问 这个就可以用树状数组做一个前缀和的处理 并且记录下每个询问的ansid 随时更新

一个询问操作当然可以被查询很多次 但是可以看出 因为二分 每个之前的修改操作都只会在一个区域被访问一次

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<map>
#include<iostream>
#include<string>
#include<queue>
#include<vector>
using namespace std;
#define pb push_back

int cnt ;

int maxx ;

struct node {
    int x , y , z ;
    int idx ;
    int tp ;

}a[50050 * 10] , q1[50000 * 10] , q2[50000 * 10];

int c[500500] ;
int lowbit(int x) {return (x&(-x)) ; }
void add(int x , int val) {
    while(x <= maxx + 20) {
        c[x] += val ;
        x += lowbit(x) ;
    }
}
int sum(int x) {
    int res = 0 ;
    while(x) {
        res += c[x] ; x -= lowbit(x) ;
    }
    return res ;
}

int n ;

int ans[50050] ;

struct ls{
    int x , id;
    int idx ;
}lss[50050 * 10];

bool cmpls(ls a , ls b) {
    return a.x < b.x ;
}
bool cmpx(node a,node b) {
    if(a.x == b.x) return a.tp < b.tp ;
    else return a.x < b.x ;
}
bool cmpy(node a,node b) {
    if(a.y == b.y) return a.tp < b.tp ;
    else return a.y < b.y ;
}

void cdq_y(int L , int R) { /// 其实这个时候 y 已经被排好序了 所以直接按照z 进行树状数组就可以了 这时候的z已经离散化了
    for(int i = L ; i <= R ; i ++ ){
        if(q2[i].tp == 0) {
            add(q2[i].z , 1) ;
        }
        else {
            int res = sum(q2[i].z) ;
            if(q2[i].tp == 1) {
                ans[q2[i].idx] += res ;
            }
            else {
                ans[q2[i].idx] -= res ;
            }
        }
    }
    for(int i = L ; i <= R ; i ++ ) {
        if(q2[i].tp == 0) {
            add(q2[i].z , -1) ;
        }
    }
}

void cdq_x(int L , int R) { /// 只要还没有剥离到可以直接利用数据结构得出答案 就应该一直分治
    if(L >= R) return ;
    int M = (L + R) / 2 ;
    cdq_x(L , M) ; cdq_x(M+1 , R) ;
    int num = L ;
    for(int i = L ; i <= M ; i ++ ) if(q1[i].tp == 0) q2[num ++ ] = q1[i] ;
    for(int i = M + 1 ; i <= R ; i ++ ) if(q1[i].tp != 0) q2[num ++ ] = q1[i] ;
    sort(q2 + L , q2 + num , cmpy) ;
    cdq_y(L , num-1) ;
}

void cdq_time(int L , int R) {
    if(L >= R) return ;
    int M = (L + R) / 2 ;
    cdq_time(L , M) ; cdq_time(M+1 , R) ;
    int num = L ;
    for(int i = L ; i <= M ; i ++ ) if(a[i].tp == 0) q1[num ++ ] = a[i] ;
    for(int i = M + 1 ; i <= R ; i ++ ) if(a[i].tp != 0) q1[num ++ ] = a[i] ;
    sort(q1 + L , q1 + num , cmpx) ;  /// 上面两行 其实就已经剥离出时间了 所以这个时候应该按照x排序 便于剥离x
    cdq_x(L , num-1) ;
}

int main (){
    int T;
    scanf("%d" , &T) ;
    while(T -- ) {
        memset(c , 0 , sizeof(c)) ;
        scanf("%d" , &n) ;
        memset(ans , 0 , sizeof(ans)) ;
        cnt = 0 ;
        int now = 0 ;
        for(int i = 1 ; i <= n ; i ++ ) {
            int tp ;
            scanf("%d" , &tp) ;
            if(tp == 1) {
                int x,y,z;
                scanf("%d%d%d" , &x , &y , &z) ;
                cnt ++ ;
                a[cnt].tp = 0 ;
                a[cnt].x = x ; a[cnt].y = y ; a[cnt].z = z ;
                a[cnt].idx = i ;
                ans[i] = -1 ;
                now ++ ;
                lss[now].id = cnt ;
                lss[now].x = z ;
            }
            else {
                int x,y,z,x2,y2,z2 ;
                scanf("%d%d%d%d%d%d" , &x , &y , &z , &x2 , &y2 , &z2) ;
                cnt ++ ; a[cnt] = {x2 , y2 , z2 , i , 1} ;
                now ++ ; lss[now].id = cnt ; lss[now].x = z2 ;

                cnt ++ ; a[cnt] = {x-1 , y2 , z2 , i , 2} ;
                now ++ ; lss[now].id = cnt ; lss[now].x = z2 ;

                cnt ++ ; a[cnt] = {x2 , y-1 , z2 , i , 2} ;
                now ++ ; lss[now].id = cnt ; lss[now].x = z2 ;

                cnt ++ ; a[cnt] = {x2 , y2 , z-1 , i , 2} ;
                now ++ ; lss[now].id = cnt ; lss[now].x = z-1 ;

                cnt ++ ; a[cnt] = {x-1 , y-1 , z2 , i , 1} ;
                now ++ ; lss[now].id = cnt ; lss[now].x = z2 ;

                cnt ++ ; a[cnt] = {x-1 , y2 , z-1 , i , 1} ;
                now ++ ; lss[now].id = cnt ; lss[now].x = z-1 ;

                cnt ++ ; a[cnt] = {x2 , y-1 , z-1 , i , 1} ;
                now ++ ; lss[now].id = cnt ; lss[now].x = z-1 ;

                cnt ++ ; a[cnt] = {x-1 , y-1 , z-1 , i , 2} ;
                now ++ ; lss[now].id = cnt ; lss[now].x = z-1 ;
            }
        }
        sort(lss+1 , lss+1+now , cmpls) ;
        int res = 0 ;
        lss[0].x = -97989 ;
        for(int i=1;i<=now;i++) {
            if(lss[i].x == lss[i-1].x) {
                a[lss[i].id].z = lss[i-1].idx;
                lss[i].idx = lss[i-1].idx;
            }
            else {
                res ++ ;
                lss[i].idx = res ;
                a[lss[i].id].z = res ;
            }
        }
        maxx = res ;
        cdq_time(1 , cnt) ;
        for(int i = 1 ; i <= n ; i ++ ) {
            if(ans[i] != -1) printf("%d
",ans[i]) ;
        }
    }
}

虽然..看起来还是挺长的...

被打哭 意识到自己其实还不会cdq 于是再次学习

发现过去写的 逐渐剥离出维度 有点暴力了

算法的正确性是没有问题的 但是每次都sort 会多一个log 可以模仿归并排序

如果我先进行了cdq(L,M) cdq(M+1,R) 那么两边的数组都是排序好了的 就可以双指针排序这个数组 省去一个sort的时间

bzoj1935

#include<stdio.h>
#include<math.h>
#include<string.h>
#include<vector>
#include<queue>
#include<map>
#include<string>
#include<iostream>
#include<algorithm>
#include<stack>
using namespace std;
#define L long long
#define pb push_back
#define lala printf("--------
");
#define ph push
#define rep(i, a, b) for (int i=a;i<=b;++i)
#define dow(i, b, a) for (int i=b;i>=a;--i)
#define fmt(i,n) if(i==n)printf("
");else printf(" ") ;
#define fi first
#define se second
template<class T> inline void flc(T &A, int x){memset(A, x, sizeof(A));}
int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}

int num;
struct node {
    int x,y;
    int time ;
    int xw;
    int id;
}q[500050 * 5],tmp[500050 * 5];
int c[10000050] ;
int lowbit(int x){return (x&(-x));}
void add(int x,int val) {while(x<=10000000) {c[x]+=val;x+=lowbit(x);}}
int sum(int x) {int res = 0 ; while(x) {res+=c[x];x-=lowbit(x);} return res ;}
int n , m ;
int ans[500050] ;
bool cmp(node a, node b) {
    if(a.x == b.x) return a.time < b.time ;
    return a.x < b.x ;
}
void cdq(int LL , int RR) {
    if(RR-LL <= 0) return ;
    int M = (LL+RR)/2 ;
    int pl = LL ;
    int pr = M+1 ;
    int pp = LL ;
    cdq(LL , M) ; cdq(M+1,RR) ;  /// 在这里 先对两边进行递归 这样可以模仿一下归并排序 省去一个sort x的复杂度
    while(pl <= M && pr <= RR) {
        if(q[pl].x < q[pr].x || (q[pl].x == q[pr].x && q[pl].time < q[pr].time)) { /// 还是一个归并排序 左边的时间一定早于右边的时间 所以左边的修改可能对右边的查询有影响 但是不一定
                                 /// 但是由于两边的x其实都已经归并排序好了 所以模仿归并的过程 面对的实际上是一个sort好的数组
            if(q[pl].xw == 0) {
                    add(q[pl].y , 1) ;
            }
            tmp[pp ++ ] = q[pl ++ ] ;
        }
        else {
            if(q[pr].xw == 1) {
                ans[q[pr].id] += sum(q[pr].y) ;
            }
            else if(q[pr].xw == 2) {
                ans[q[pr].id] -= sum(q[pr].y) ;
            }
            tmp[pp ++ ] = q[pr ++ ] ;
        }
    }
    while(pl <= M) {
        if(q[pl].xw == 0) add(q[pl].y , 1) ;
        tmp[pp ++ ] = q[pl ++ ] ;
    }
    while(pr <= RR) {
        if(q[pr].xw == 1)
            ans[q[pr].id] += sum(q[pr].y) ;
        else if(q[pr].xw == 2)
            ans[q[pr].id] -= sum(q[pr].y) ;
        tmp[pp ++ ] = q[pr ++ ] ;
    }
    for(int i = LL ; i <= M ; i ++ ) { /// 得消除掉c的操作 保持为0
        if(q[i].xw == 0)
            add(q[i].y , -1) ;

    }
    for(int i = LL ; i <= RR ; i ++ ) q[i] = tmp[i] ; /// 归并排序 数组复制
}

int main () {
    n=read();m=read();
    flc(c,0);
    num=0;
    rep(i,1,n) {
        int x=read();int y=read();
        x ++ ; y ++ ;
        num++;
        q[num]={x,y,num,0,0} ;
    }
    rep(i,1,m) {
        int x1=read();int y1=read();int x2=read();int y2=read();
        x1 ++ ; y1 ++ ; x2 ++ ; y2 ++ ;
        q[++num]={x2,y2,num,1,i};
        q[++num]={x1-1,y1-1,num,1,i};
        q[++num]={x1-1,y2,num,2,i};
        q[++num]={x2,y1-1,num,2,i};
    }
    flc(ans,0);
    cdq(1,num) ;
    rep(i,1,m) {
        printf("%d
" , ans[i]) ;
    }
}

 cdq的复杂度是每层一个logn 如果直接sort的话其实也是这个复杂度 归并排序会省去一个常数

原文地址:https://www.cnblogs.com/rayrayrainrain/p/6686672.html