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;
}