CDQ分治

算法简介

用于解决三维数点问题:

给定形如你个 \((x,y,z)\) 的三维坐标,然后让你求有多少个点三个维度的坐标都小于这个点

做法:用分治思想将其转化为二维数点

二位数点:先用排序解决掉第一维,再用树状数组维护第二维小于它的点数

分治思想:我们把一个区间划分为两半,我们只统计左半部分修改对右半部分询问的贡献,然后一直递归下去

为什么可做?因为我们将区间无限拆分下去,总能将每一组询问和查询分到两边去,而且也保证了修改与查询之间的大小关系

然后注意树状数组用完后暴力清空肯定超时,我们对所有修改了的点清空即可

模板:洛谷P3810

代码实现(使用vector可以减少实现的难度):

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,k;
int tr[N],ans[N],cnt[N];
struct operation{
    int a,b,c,op,i;
};
bool cmp(operation x,operation y){
    return x.c<y.c||(x.c==y.c&&x.op<y.op);
}
int lowbit(int x){
    return x&(-x);
}
void add(int x,int z){
    for(;x<=k;x+=lowbit(x)){
        tr[x]+=z;
    }
}
int query(int x){
    int res=0;
    for(;x;x-=lowbit(x)){
        res+=tr[x];
    }
    return res;
}
void cdq(int l,int r,vector<operation>&op){
    if(l==r){
        for(auto i:op){
            if(i.op==1)  add(i.b,1);
            else  ans[i.i]+=query(i.b);
        }
        for(auto i:op)  if(i.op==1)  add(i.b,-1);
        return;
    }
    int mid=(l+r)>>1;
    for(auto i:op){
        if(i.op==1&&i.a<=mid)  add(i.b,1);
        if(i.op==2&&i.a>mid)  ans[i.i]+=query(i.b);
    }
    for(auto i:op){
        if(i.op==1&&i.a<=mid)  add(i.b,-1);
    }
    vector<operation>lop,rop;
    for(auto i:op){
        if(i.a<=mid)  lop.push_back(i);
        else  rop.push_back(i);
    }
    cdq(l,mid,lop);
    cdq(mid+1,r,rop);
}
int main(){
    scanf("%d%d",&n,&k);
    vector<operation>op;
    for(int i=1;i<=n;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        op.push_back({a,b,c,1,i});
        op.push_back({a,b,c,2,i});
    }
    sort(op.begin(),op.end(),cmp);
    cdq(1,k,op);
    for(int i=1;i<=n;i++){
        cnt[ans[i]-1]++;
    }
    for(int i=0;i<n;i++){
        printf("%d\n",cnt[i]);
    }
}

洛谷P4390

第三维为时间,然后将平面上求一个矩形,拆成四个前缀的查询,就是模板了

#include<bits/stdc++.h>
using namespace std;
const int N=2e6+5;
struct operation{
    int a,b,c,op,num;
};
int g,w,u,v,a,x,y,cnt,tot;
int tr[N],ans[N];
bool cmp(operation x,operation y){
    return x.c<y.c||(x.c==y.c&&x.op<y.op);
}
int lowbit(int x){
    return x&(-x);
}
void add(int x,int z){
    for(;x<=w;x+=lowbit(x)){
        tr[x]+=z;
    }
}
int query(int x){
    int res=0;
    for(;x;x-=lowbit(x)){
        res+=tr[x];
    }
    return res;
}
void cdq(int l,int r,vector<operation>&op){
    if(l==r){
        for(auto i:op){
            if(i.op==1)  add(i.b,i.num);
            else if(i.op==2)  ans[i.num]+=query(i.b);
            else  ans[i.num]-=query(i.b);
        }
        for(auto i:op)  if(i.op==1)  add(i.b,-i.num);
        return;
    }
    int mid=(l+r)>>1;
    for(auto i:op){
        if(i.op==1&&i.a<=mid)  add(i.b,i.num);
        if(i.op==2&&i.a>mid)  ans[i.num]+=query(i.b);
        if(i.op==3&&i.a>mid)  ans[i.num]-=query(i.b);
    }
    for(auto i:op){
        if(i.op==1&&i.a<=mid)  add(i.b,-i.num);
    }
    vector<operation>lop,rop;
    for(auto i:op){
        if(i.a<=mid)  lop.push_back(i);
        else  rop.push_back(i);
    }
    cdq(l,mid,lop);cdq(mid+1,r,rop);
    return;
}
int main(){
    scanf("%d%d",&g,&w);
    w++;
    vector<operation>op;
    while(1){
        cnt++;
        scanf("%d",&g);
        if(g==3)  break;
        if(g==1){
            scanf("%d%d%d",&u,&v,&a);
            u++,v++;
            op.push_back((operation){u,v,cnt,1,a});
        }
        else{
            scanf("%d%d%d%d",&u,&v,&x,&y);
            u++,v++,x++,y++;
            tot++;
            op.push_back((operation){x,y,cnt,2,tot});
            op.push_back((operation){u-1,v-1,cnt,2,tot});
            op.push_back((operation){u-1,y,cnt,3,tot});
            op.push_back((operation){x,v-1,cnt,3,tot});
        }
    }
    sort(op.begin(),op.end(),cmp);
    cdq(1,w,op);
    for(int i=1;i<=tot;i++){
        printf("%d\n",ans[i]);
    }
    return 0;
}

洛谷P5621

CDQ分治套CDQ再加上dp

首先它有4维的限制,所以我们考虑CDQ套CDQ,如何做呢?

就是用一层CDQ让它先满足第一维的限制,再用下一层再满足下一维

考虑dp怎么做,用一个所有坐标都小于它的点递推到这个点,所以我们用树状数组维护dp值,然后求前缀最大值,然后清空的话还是把所有修改了的值清空即可

然后因为是dp递推所以要考虑递归顺序,先进行左边的递归再中间的再右边的,然后我们的第一个CDQ的限制条件要下传到第二个CDQ中

还有要注意的是因为题目中都会有负数,所以树状数组维护那一维要离散化,还有就是用CDQ维护那两维要从区间是 \([-N,N]\) 然后直接这么做的话会出现一些奇奇怪怪的问题,我没有调出来,还是全部乖乖离散化吧

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5,inf=1e9;
int n,suma,sumb,sumc;
int tr[N+5],dp[N+5];
int cnta[N+5],cntb[N+5],cntc[N+5];
map<int,int>mpa,mpb,mpc;
struct operation{
    int a,b,c,d,num,i;
};
int lowbit(int x){
    return x&(-x);
}
void add(int x,int z){
    for(;x<=sumc;x+=lowbit(x)){
        tr[x]=max(tr[x],z);
    }
}
void clear(int x){
    for(;x<=sumc;x+=lowbit(x)){
        tr[x]=0;
    }
}
int query(int x){
    int res=-inf;
    for(;x;x-=lowbit(x)){
        res=max(tr[x],res);
    }
    return res;
}
void cdq2(int l,int r,vector<operation>&op,int x,int y){
    if(op.empty())  return;
    // printf("2:%lld %lld\n",l,r);
    if(l==r){
        for(auto i:op){
            if(i.a>=y)  dp[i.i]=max(dp[i.i],query(i.c)+i.num);
            if(i.a<=x)  add(i.c,dp[i.i]);
        }
        for(auto i:op){
            if(i.a<=x)  clear(i.c);
        }
        return;
    }
    int mid=(l+r)>>1;
    vector<operation>lop,rop;
    for(auto i:op){
        if(i.b<=mid)  lop.push_back(i);
        else  rop.push_back(i);
    }
    for(auto i:op){
        if(i.a<=x&&i.b<=mid)  add(i.c,dp[i.i]);
		if(i.a>=y&&i.b>mid)  dp[i.i]=max(dp[i.i],query(i.c)+i.num);
    }
    for(auto i:lop){
        if(i.a<=x)  clear(i.c);
    }
    cdq2(l,mid,lop,x,y);
    cdq2(mid+1,r,rop,x,y);
}
void cdq1(int l,int r,vector<operation>&op){
	if(op.empty())  return;
	// printf("1:%lld %lld\n",l,r);
    if(l==r){
        cdq2(1,sumb,op,l,l);
        return;
    }
    int mid=(l+r)>>1;
    vector<operation>lop,rop;
    for(auto i:op){
        if(i.a<=mid)  lop.push_back(i);
        else  rop.push_back(i);
    }
    cdq1(l,mid,lop);
    cdq2(1,sumb,op,mid,mid+1);
    cdq1(mid+1,r,rop);
}
bool cmp(operation x,operation y){
    return x.d<y.d;
}
signed main(){
    scanf("%lld",&n);
    vector<operation>op;
    for(int i=1;i<=n;i++){
        int a,b,c,d,num;
        scanf("%lld%lld%lld%lld%lld",&a,&b,&c,&d,&num);
        cnta[i]=a;cntb[i]=b;cntc[i]=c;
        dp[i]=num;
        op.push_back((operation){a,b,c,d,num,i});
    }
    sort(cnta+1,cnta+1+n);
    sort(cntb+1,cntb+1+n);
    sort(cntc+1,cntc+1+n);
    suma=unique(cnta+1,cnta+1+n)-cnta-1;
    sumb=unique(cntb+1,cntb+1+n)-cntb-1;
    sumc=unique(cntc+1,cntc+1+n)-cntc-1;
    for(int i=1;i<=suma;i++){
    	// printf("%lld %lld\n",cnta[i],i);
        mpa[cnta[i]]=i;
    }
    for(int i=1;i<=sumb;i++){
        mpb[cntb[i]]=i;
    }
    for(int i=1;i<=sumc;i++){
        mpc[cntc[i]]=i;
    }
    for(auto &i:op){//这里修改时要加上取地址符,这样才能修改到正确的位置
        i.a=mpa[i.a];
        i.b=mpb[i.b];
        i.c=mpc[i.c];
    }
    sort(op.begin(),op.end(),cmp);
    // for(auto i:op){
    	// printf("%lld %lld %lld %lld %lld %lld\n",i.a,i.b,i.c,i.d,i.num,i.i);
    // }
    for(int i=1;i<=n;i++)  tr[i]=0;
    cdq1(1,suma,op);
    int ans=-inf;
    for(int i=1;i<=n;i++){
        // printf("%lld\n",dp[i]);
        ans=max(dp[i],ans);
    }
    printf("%lld",ans);
    return 0;
}
posted @ 2024-12-09 20:54  daydreamer_zcxnb  阅读(7)  评论(0编辑  收藏  举报