CDQ&整体二分-三维偏序(陌上花开)

题目描述

本文讲cdq整体二分的思路与做法。=分治VS数据结构

其实维度这一方面,空间几何可以是维度,像时间这样有规定顺序的词语也可能是维度。

cdq

三维偏序,一般可以用一维一维的消。可以用cdq嵌套,线段树嵌套来做,我还不会,这里用排序一维,cdq一维,树状数组一维,感觉对比嵌套在思维拓展方面有帮助。

题面的 f [ i ] f[i] f[i]满足 a [ j ] ≤ a [ i ] , b [ j ] ≤ b [ i ] , c [ j ] ≤ c [ i ] a[j]\leq a[i],b[j]\leq b[i],c[j]\leq c[i] a[j]a[i],b[j]b[i],c[j]c[i],因为元素相同的情况在分治中会出现漏解,所以cdq分治在出现相同元素时常做去重处理。

做法:

先按去重后序列以a为第一关键字 注意不是唯一关键字 排序,

然后常规分治,之后将左右区间分别按照b为(第一)关键字排序,

这时因为我们要根据左区间给右区间贡献,而左区间的a值一定都小于右区间的a值,所以a无影响。按照b归并排序的同时更新树状数组,并得到右区间的贡献最后记得清空树状数组

代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#define M (l+r>>1)
using namespace std;
const int N=1e5+5;
int n,k,tot,ans[N],tr[N<<1];
struct qh{
    int a,b,c,ans,cnt;
}in[N],a[N];
bool c1(qh a,qh b){return a.a==b.a?a.b==b.b?a.c<b.c:a.b<b.b:a.a<b.a;}
bool c2(qh a,qh b){return a.b==b.b?a.c<b.c:a.b<b.b;}
int lowbit(int x){return x&(-x);}
void add(int x,int y){for(int i=x;i<=k;i+=lowbit(i)) tr[i]+=y;return ;}
int gs(int x){int res=0;for(int i=x;i;i-=lowbit(i)) res+=tr[i];return res;}
void cdq(int l,int r){
    if(l==r) return ;
    cdq(l,M);cdq(M+1,r);
    sort(a+l,a+M+1,c2);sort(a+M+1,a+r+1,c2);
    int q1=l,q2=M+1;
    while (q1<=M&&q2<=r){
        if(a[q1].b<=a[q2].b){
            add(a[q1].c,a[q1].cnt);
            q1++;
        }
        else{
            a[q2].ans+=gs(a[q2].c);
            q2++;
        }
    }
    while (q2<=r){
        a[q2].ans+=gs(a[q2].c);
        q2++;
    }
    for(int i=l;i<q1;i++) add(a[i].c,-a[i].cnt);        //*******
    return ;
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d%d%d",&in[i].a,&in[i].b,&in[i].c);
    sort(in+1,in+n+1,c1);
    int c=0;
    for(int i=1;i<=n;i++){
        c++;
        if(in[i].a!=in[i+1].a||in[i].b!=in[i+1].b||in[i].c!=in[i+1].c) a[++tot]=(qh){in[i].a,in[i].b,in[i].c,0,c},c=0;
    }
    cdq(1,tot);
    for(int i=1;i<=tot;i++) ans[a[i].ans+a[i].cnt-1]+=a[i].cnt;
    for(int i=0;i<n;i++) printf("%d\n",ans[i]);
    return 0;
}
//cdq分治:相同元素做去重处理

开不开O2区别不大。
在这里插入图片描述


整体二分

例题

题目大意:

给定一个长度为 n ( n <= 50,000 ) 的数组 a[1] , a[2] … a[n] 和 q ( q <= 10,000 ) 此询问,每次询问:

  1. Q i j k 表示区间 [i,j] 中第 k 小的数是多少,并输出这个数

  2. C i t 表示将第 i 个数改为 t

样例输入
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3
样例输出
3
6

整体二分,顾名思义,就是把所有的东西拿来一起二分。在这道题里我们还要开一棵树状数组。

  1. 修改操作等于删除+添加
  2. 查询操作=二分答案
  3. 修改和加入按照所在位置二分
  4. 顺序处理所有操作

如果哪一步不懂就看代码吧。

#include<cstdio>
#include<iostream>
#define M (l+r>>1)
using namespace std;
const int N=5e4+5;
int n,q,tot,qtot,a[N],ans[N],tr[N],cnt[N<<1];
struct qh{
    int x,y,z,t,ad,s;
}E[N<<1],El[N<<1],Er[N<<1],op;
inline int Rd(){
    int s=0,w=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
    while (ch>='0'&&ch<='9') s=(s<<1)+(s<<3)+ch-'0',ch=getchar();
    return s*w;
}
inline int lowbit(int x){return x&(-x);}
inline void add(int x,int y){
    for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=y;
    return ;
}
inline int gs(int x){
    int res=0;
    for(int i=x;i;i-=lowbit(i)) res+=tr[i];
    return res;
}
inline void init(int h,int t,int l,int r){
    if(h>t) return ;
    if(l==r){                    //pd l==r
        for(int i=h;i<=t;i++) if(E[i].t==3) ans[E[i].ad]=l;
        return ;
    }
    for(int i=h;i<=t;i++){       //ls
        if(E[i].t==1&&E[i].y<=M) add(E[i].x,1);
        else if(E[i].t==2&&E[i].y<=M) add(E[i].x,-1);
        else if(E[i].t==3) cnt[i]=gs(E[i].y)-gs(E[i].x-1);
    }
    for(int i=h;i<=t;i++){       //empty
        if(E[i].t==1&&E[i].y<=M) add(E[i].x,-1);
        else if(E[i].t==2&&E[i].y<=M) add(E[i].x,1);
    }
    int l1=0,r1=0;
    for(int i=h;i<=t;i++){       //merge
        if(E[i].t==3){
            if(E[i].s+cnt[i]>=E[i].z) El[l1++]=E[i];
            else E[i].s+=cnt[i],Er[r1++]=E[i];
        }
        else if(E[i].y<=M) El[l1++]=E[i];
        else Er[r1++]=E[i];
    }
    for(int i=0;i<l1;i++) E[h+i]=El[i];
    for(int i=0;i<r1;i++) E[h+l1+i]=Er[i];
    init(h,h+l1-1,l,M);
    init(h+l1,t,M+1,r);
}
int main(){
    n=Rd();q=Rd();
    for(int i=1;i<=n;i++){
        a[i]=Rd();
        op.x=i;op.y=a[i];op.t=1;
        E[++tot]=op;
    }
    while (q--){
        char ch=getchar();
        int x,y,z;
        while (ch!='Q'&&ch!='C') ch=getchar();
        if(ch=='Q'){
            x=Rd();y=Rd();z=Rd();
            op.x=x;op.y=y;op.z=z;op.t=3;op.ad=++qtot;
            E[++tot]=op;
        }
        else{
            x=Rd();y=Rd();
            op.x=x;op.y=a[x];op.t=2;E[++tot]=op;
            op.x=x;op.y=y;op.t=1;E[++tot]=op;
            a[x]=y;
        }
    }
    init(0,tot,0,1e9);
    for(int i=1;i<=qtot;i++) printf("%d\n",ans[i]);
    return 0;
}

结构跟权值线段树还挺像

总结

整体二分的 思想 是同时对处理区间和答案进行二分。

CDQ分治的 思想 是用处理方式(关键字)进行排序,然后对时间进行二分。

整体二分 可以用于询问操作一样,而且可以二分答案解决的问题(具有单调性,如时间可以二分)。

CDQ分治 可以用于 求多维偏序问题。

整体二分从上往下做,CDQ分治从下往上做。

两种分治算法都比较暴力,它们的优点是代码短而清晰,缺点是复杂度玄学,必须离线。

所以,这次还是没能决出胜负啊。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值